令 \(w_{(u, v)}\) 为边 \((u, v)\) 的边权。
考虑到对于一条边进行操作影响的是有公共点的边,于是一个想法是把边权转到点权,用点权来表示边权。
于是考虑对于每个点构造 \(w_u\) 使得 \(w_{(u, v)} = w_u\oplus w_v\)。
因为这是一颗树,所以一定存在合法的构造。
其实到了这里,这种构造的方案还只是一种猜想,并不确定是否正确(能正确的表述操作)。
接下来考虑操作一条边 \((u, v)\)。
就相当于是 \(w_u\leftarrow w_u\oplus w_{(u, v)} = w_u\oplus (w_u\oplus w_v) = w_v\),同样的,有 \(w_v\leftarrow w_u\)。
然后能发现此时对于边 \((u, v)\),边权依旧是 \((w_u, w_v)\),对于边 \((u, x)\),边权从 \(w_u\oplus w_x\) 变为了 \(w_v\oplus w_x\),刚好异或的是 \(w_u\oplus w_v = w_{(u, v)}\)。
所以能够知道这种构造方案是正确的。
同时能够发现,这时操作变为了交换相邻两点的点权。
于是可以知道,只要对于每一种点权出现次数的次数都相同,两种局面就一定是可以互相操作出的。
那么只需要把初始边权与终止边权带入,各自跑出一个合法的点权 \(w_{u, 0 / 1}\),向上面这样判定就行了。……吗?
考虑到 \(w_{(u, v)} = w_u\oplus w_v = (w_u\oplus x)\oplus (w_v\oplus x)\),这意味着有可能初始局面会在所有点权都异或上一个值 \(x\) 后才会变成终止局面。
但考虑到若存在合法的 \(x\),一定有 \((w_{1, 0}\oplus x)\oplus \cdots \oplus (w_{n, 0}\oplus x) = w_{1, 1}\oplus \cdots \oplus w_{n, 1}\)。
又因为 \(n\) 为奇数,一定能得到一个 \(x\)。
但是需要注意上面这个条件不是充要条件。
即上面的得到的 \(x\) 依然可能是不合法的,不能保证一一对应。(\(w_{1 \sim 3, 0 / 1} = \begin{bmatrix}0 & 0\\ 0 & 1\\ 0 & 1 \end{bmatrix}\),此时可以得到 \(x = 0\),但代入能发现不合法)。
那么再代入检查一遍是不是一一对应就行了。
时间复杂度 \(\mathcal{O}(n\log n)\),如果最后的检查用基排可以 \(\mathcal{O}(n)\)。
#include<bits/stdc++.h>
const int maxn = 1e5 + 10;
int a[maxn][2];
std::vector<std::tuple<int, int, int> > to[maxn];
void dfs(int u, int fa) {
for (auto [v, val0, val1] : to[u]) {
if (v == fa) continue;
a[v][0] = a[u][0] ^ val0;
a[v][1] = a[u][1] ^ val1;
dfs(v, u);
}
}
int main() {
int n; scanf("%d", &n);
for (int i = 1, u, v, val0, val1; i < n; i++) {
scanf("%d%d%d%d", &u, &v, &val0, &val1);
to[u].emplace_back(v, val0, val1);
to[v].emplace_back(u, val0, val1);
}
dfs(1, 0);
int val = 0;
for (int i = 1; i <= n; i++)
val ^= a[i][0] ^ a[i][1];
for (int i = 1; i <= n; i++)
a[i][0] ^= val;
std::map<int, int> mp;
for (int i = 1; i <= n; i++)
mp[a[i][0]]++, mp[a[i][1]]--;
for (auto [x, y] : mp)
if (y)
return puts("NO"), 0;
return puts("YES"), 0;
}
标签:Atcoder,XOR,int,边权,Tree,点权,oplus,val0,val1
From: https://www.cnblogs.com/rizynvu/p/18334334