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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章