AHOI2018 Day1

以前寫過一份 Day2 的。

這套題難度很大。當年賽場上貌似得分率不高。

尋寶遊戲

\(\vee\)\(\land\) 看成 0/1,與原序列對比發現變成答案中的 0/1 等價於比較數字的大小。最後排序即可。

#include <bits/stdc++.h>
using std::sort;
const int N = 1005, M = 5005, P = 1e9 + 7;
int n, m, Q, a[M][N], l[M]; char ch[M];
bool cmp(int x, int y) {
	for (int i = n; i; i--)
		if (a[x][i] != a[y][i]) return a[x][i] < a[y][i];
	return 0;
}
int main() {
	scanf("%d%d%d", &n, &m, &Q);
	for (int i = 1; i <= n; i++) {
		scanf("%s", ch+1);
		for (int j = 1; j <= m; j++)
			a[j][i] = ch[j]-'0';
	}
	for (int i = 1; i <= m; i++) l[i] = i;
	sort(l+1, l+m+1, cmp);
	while (Q--) {
		int x = 0, y = 0, ok = 1;
		scanf("%s", ch+1);
		for (int i = 1; i <= m; i++) {
			if (ch[l[i]] == '1' && !x) x = i;
			if (ch[l[i]] == '0') y = i;
		}
		if (x) for (int i = n; i; i--)
			if (a[l[x]][i] != a[l[y]][i]) { 
				if (a[l[x]][i] < a[l[y]][i]) ok = 0;
				break;
			}
		if (!ok) puts("0");
		else {
			int ans = x?0:1;
			for (int i = n; i; i--)
				ans = ((2*ans+a[l[x]][i]-a[l[y]][i])%P + P)%P;
			printf("%d\n", ans);
		}
	}
	return 0;
}

轉盤

兩處縮放:第一處在選取解時,我們把選取每個物品的時間在總時間不變的情況下儘可能推遲。發現當一個遞增序列的值處處大於時間則遞增序列最大值即爲答案。

第二處在於發現上述問題的性質,破環成鏈再對下標差分後,求 每個長度爲 \(n\) 的區間最大值+一個與下標相關的偏移量 前面這個整體的 最小值。分析發現可以進一步把區間長度放到後綴長度。這一點很重要。

後綴max遞減,最後發現實際上就是一個樓房重建問題。複雜度 \(\mathcal O(n\log^2 n)\)

#include <bits/stdc++.h>
using std::max;
using std::min;
const int N = 100005;
int n, m, p, las;
#define lc (o << 1)
#define rc (o << 1 | 1)
int ma[N*4], val[N*4], fix[N*4];
int query(int o, int l, int r, int v) {
	if (l == r) return v < ma[o] ? val[o] : v+l;
	int mid = l+r>>1;
	return v > ma[rc] ? min(query(lc, l, mid, v), v+mid+1) : min(fix[o], query(rc, mid+1, r, v));
}
void pushup(int o, int l, int r) {
	int mid = l+r>>1;
	ma[o] = max(ma[lc], ma[rc]);
	val[o] = min(val[rc], fix[o] = query(lc, l, mid, ma[rc]));
}
void modify(int o, int l, int r, int p, int v) {
	if (l == r) { ma[o] = v; val[o] = v+p; return; }
	int mid = l+r>>1;
	p <= mid ? modify(lc, l, mid, p, v) : modify(rc, mid+1, r, p, v);
	pushup(o, l, r);
}
int main() {
	scanf("%d%d%d", &n, &m, &p);
	for (int i = 1; i <= n; i++) { int x; scanf("%d", &x), modify(1, 1, n, i, x-i); }
	printf("%d\n", las = query(1, 1, n, ma[1]-n) + n - 1);
	while (m--) {
		int x, y; scanf("%d%d", &x, &y); p && (x ^= las, y ^= las);
		modify(1, 1, n, x, y-x);
		printf("%d\n", las = query(1, 1, n, ma[1]-n) + n - 1);
	}
	return 0;
}

毒瘤

合理刪去 \(n-m+1\) 條邊後是一棵樹。這樣子做 DP 還需要關心去掉的 \(n-m+1\) 對限制。

枚舉這些限制,總方案數爲 \(3\times 2^{n-m+1}=\mathcal O(2^{n-m+1})\)。給上限制再做 DP 每次都要 \(\mathcal O(n)\) 的複雜度,太屑了。

考慮優化,注意到 DP 實際上只跟這些點構成的虛樹有關。然後就可以類似 NOIP2018 的 D2T3 一樣搞,只不過這裏不用倍增。仍然要注意維護 DP 值的順序。

總複雜度 \(\mathcal O(n + s2^s)\),這裏 \(s=n-m+1\)

#include <bits/stdc++.h>
using std::vector;
const int N = 100020, P = 998244353;
int n, m, ans = 0;
struct Edge {
	int v, nxt;
} e[N*2];
int edges = 0, G[N];
void adde(int u, int v) {
	e[edges++] = (Edge){v, G[u]}, G[u] = edges-1;
}
int fa[N];
int fset(int x) { return fa[x] == x ? x : fa[x] = fset(fa[x]); }
void merge(int x, int y) { fa[fset(x)] = fset(y); }
int tag[N], f[N][2], g[N][2][2];
vector<int> e2[N], l;
int dfs1(int u, int fa) {
	f[u][0] = f[u][1] = 1;
	for (int i = G[u], v; ~i; i = e[i].nxt) {
		if (v = e[i].v, v == fa) continue;
		int t = dfs1(v, u);
		f[u][0] = 1LL * f[u][0] * (f[v][0] + f[v][1]) % P;
		f[u][1] = 1LL * f[u][1] * f[v][0] % P;
		if (t) e2[u].push_back(t);
	}
	if (!tag[u]) tag[u] = e2[u].size() > 1 ? u : e2[u].size() ? e2[u][0] : 0;
	return tag[u];
}
void dfs2(int u, int fa) {
	for (int i = G[u], v; ~i; i = e[i].nxt) {
		if (v = e[i].v, v == fa) continue;
		dfs2(v, u); int x = tag[v];
		if (!x) continue;
		g[x][0][0] = (g[x][0][0] + g[x][0][1]) % P, g[x][0][1] = (g[x][0][0] + P - g[x][0][1]) % P;
		g[x][1][0] = (g[x][1][0] + g[x][1][1]) % P, g[x][1][1] = (g[x][1][0] + P - g[x][1][1]) % P;
	}
	g[u][0][0] = g[u][1][1] = 1;
	for (int i = G[u], v; ~i; i = e[i].nxt) {
		if (v = e[i].v, v == fa || tag[v]) continue;
		int x = tag[u];
		g[x][0][0] = 1LL * g[x][0][0] * (f[v][0] + f[v][1]) % P;
		g[x][0][1] = 1LL * g[x][0][1] * f[v][0] % P;
		g[x][1][0] = 1LL * g[x][1][0] * (f[v][0] + f[v][1]) % P;
		g[x][1][1] = 1LL * g[x][1][1] * f[v][0] % P;
	}
}
int dp[N][2], col[N];
void calc(int u) {
	dp[u][0] = dp[u][1] = 1;
	if (~col[u]) dp[u][col[u]^1] = 0;
	for (int i = 0, v; i < e2[u].size(); i++) {
		v = e2[u][i]; calc(v);
		dp[u][0] = 1LL * dp[u][0] * ((1LL * dp[v][0] * g[v][0][0] + 1LL * dp[v][1] * g[v][1][0]) % P) % P;
		dp[u][1] = 1LL * dp[u][1] * ((1LL * dp[v][0] * g[v][0][1] + 1LL * dp[v][1] * g[v][1][1]) % P) % P;
	}
}
int main() {
	scanf("%d%d", &n, &m);
	memset(G, -1, sizeof G);
	for (int i = 1; i <= n; i++) fa[i] = i;
	for (int i = 1; i <= m; i++) {
		int u, v; scanf("%d%d", &u, &v);
		if (fset(u) == fset(v)) l.push_back(u), l.push_back(v), tag[u] = u, tag[v] = v;
		else adde(u, v), adde(v, u), merge(u, v);
	}
	tag[1] = 1; dfs1(1, 0); dfs2(1, 0);
	memset(col, -1, sizeof col);
	for (int S = 0; S < 1<<(m-n+1)*2; S++) { // 這裏不小心寫成了2^2s的枚舉,不過人沒事
		for (int i = 0; i < (m-n+1)*2; i++) col[l[i]] = -1;
		int ok = 1;
		for (int i = 0; i < (m-n+1)*2; i++) {
			if (~col[l[i]] && col[l[i]] != (S >> i & 1)) { ok = 0; break; }
			col[l[i]] = S >> i & 1;
		}
		for (int i = 0; i < (m-n+1)*2; i += 2)
			if (col[l[i]] && col[l[i+1]]) { ok = 0; break; }
		if (!ok) continue;
		calc(1);
		ans = (ans + 1LL * dp[1][0] * g[1][0][0] + 1LL * dp[1][1] * g[1][1][1]) % P;
	}
	printf("%d", ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章