CF1790E - XOR Tree
题意
给定一颗无根树,在可以改变任意一个点的点权操作基础上,让树上任意简单路径的异或和不为 \(0\) ,问最少需要多少次操作。
思路
假设某个点为根,设 \(pre_x\) 为 \(x\) 点到根的树上前缀异或和, \(a_x\) 为 \(x\) 的点权,则 \(x\) 和 \(y\) 之间简单路径的异或和即为 \(pre_x \oplus pre_y \oplus a_{lca_{x,y}}\) 。那么,自然题目可以转化为 \(pre_x \oplus pre_y \oplus a_{lca_{x,y}} \neq 0\) 。
假设出现违反题目的操作,那么我们可以将 \(a_{lca_{x,y}}\) 改成一个任意大的值,使得该点的子树前缀和无效(或者说加上了一个非常大的值,不会违反题目的情况)。
因此,可以发现,我们并不需要真的去修改点权,对其的修改相当于将以其为根的子树丢弃。那么,我们就可以设当前遍历到的节点为 \(x\) ,是否存在为其子树的节点 \(u\) 和 \(v\) ,使得 \(pre_u \uplus a_x = pre_v\) 。对于这个过程,我们可以将前缀异或和存在 set 之中, dfs 儿子节点后,对两个集合,查看是否出现上述情况,如果出现该情况,那么说明这个点要被修改,信息待会要被掉了。枚举完条件后,需要合并一下信息,相当于凑齐子树信息。这样再一次 dfs 一遍即可。
要注意的一个点是,由于涉及到两个集合合并,因此可以使用启发式合并的方式合并集合。
代码
auto Main() -> void {
int n;
cin >> n;
vector<int> a(n), pre(n);
for (int i = 0; i < n; i += 1) {
cin >> a[i];
pre[i] = a[i];
}
vector adj(n, vector<int>{});
for (int i = 0; i < n - 1; i += 1) {
int u, v;
cin >> u >> v;
u -= 1;
v -= 1;
adj[u].emplace_back(v);
adj[v].emplace_back(u);
}
auto dfs1 = [&](auto &self, int from, int come) -> void {
for (auto to : adj[from]) {
if (to != come) {
pre[to] ^= pre[from];
dfs(dfs, to, from);
}
}
};
dfs1(dfs1, 0, -1);
vector<set<int>> st(n);
int ans = 0;
auto dfs2 = [&](auto &dfs, int from, int come) -> void {
st[from].emplace(pre[from]);
bool need = false;
for (auto to : adj[from]) {
if (to == come) {
continue;
}
dfs(dfs, to, from);
if (need) continue;
if (st[from].size() < st[to].size()) {
swap(st[from], st[to]);
}
for (auto x : st[to]) {
if (st[from].contains(x ^ a[from])) {
need = true;
break;
}
}
st[from].merge(st[to]);
}
ans += need;
if (need) {
st[from].clear();
}
};
dfs2(dfs2, 0, -1);
cout << ans << '\n';
}
标签:pre,CF1790E,XOR,int,题解,dfs,st,auto,adj
From: https://www.cnblogs.com/FlandreScarlet/p/17770783.html