设 \(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;
}
标签:head,set,val,int,点权,cnt,CF1709E
From: https://www.cnblogs.com/Kobe303/p/16824155.html