CF1709E

\(val_u\) 表示树中 \(1\)\(u\) 路径上的点权异或和。

那么 \(u\)\(v\) 路径上的点权异或和为 \(0\) 说明 \(val_u\oplus val_v\oplus a_{\text{lca}(u,v)}=0\)

不难发现因为值域没有限制,所以改变了点 \(u\) 的点权之后,一定不存在以 \(u\) 子树中的点为端点的点权异或和为 \(0\) 的路径。

那么对每个点开一个 set,保存其子树中 \(val\) 的集合。如果最终一个点必须被修改,那么它的 set 就变成空的。

设当前点为 \(u\)\(u\) 的当前集合为 \(S\),它的某个儿子的集合为 \(T\),枚举 \(T\) 中每个元素 \(x\),如果 \(S\) 中存在 \(a_u\oplus x\),那么最终 \(u\) 就必须被修改。

时间复杂度 \(\mathcal O(n^2\log n)\),启发式合并 set 就能做到时间复杂度 \(\mathcal O(n\log^2n)\),空间复杂度 \(\mathcal O(n\log n)\)

Code:

#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n;
int a[N], val[N];
int head[N], ver[N*2], nxt[N*2], cnt;
int ans;
set <int> S[N];

void add(int u, int v) {
	ver[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}

void dfs(int u, int fa) {
	bool flag = 0; S[u].insert(val[u]);
	for (int i = head[u]; i; i = nxt[i]) {
		int v = ver[i];
		if (v == fa) continue;
		val[v] = val[u] ^ a[v], dfs(v, u);
		if (S[u].size() < S[v].size()) swap(S[u], S[v]);
		for (auto x : S[v]) if (S[u].find(a[u] ^ x) != S[u].end()) flag = 1;
		for (auto x : S[v]) S[u].insert(x);
	}
	if (flag) ++ans, S[u].clear();
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1, u, v; i < n; ++i) scanf("%d%d", &u, &v), add(u, v), add(v, u);
	val[1] = a[1], dfs(1, 0);
	printf("%d", ans);
	return 0;
}
posted @ 2022-10-25 10:52  Kobe303  阅读(19)  评论(0编辑  收藏  举报