[AGC052B] Tree Edges XOR 做题笔记
Erlija Gäte
神仙题
题意
有一个拥有奇数个节点的带边权树。可以进行无限次如下的操作:
- 选择一条边权为 \(w_i\) 的边,然后让所有和这条边相邻的边的边权都异或上 \(w_i\)。(两条边相邻,当且仅当这两条边恰好又一个公共点)
每一个边有一个期望目标 \(t_i\),问能否在操作后让所有边的边权都等于这条边的期望目标。
\(n\leq 10^5, w_i, t_i\leq 10^7\)。
解法
边权的变化十分复杂,不具有良好的性质。考虑将一个用一个新的特征信息去刻画边权信息,要求能够建立边权信息确定的状态到特征信息确定的状态的单射,并且边权的变化所对应的特征信息的变化具有好的性质。
为了让一次边权操作对特征信息的改动尽量小,可以尝试将特征信息建立在点上。以期达到只有 \(u, v\) 这两个点的特征信息变化的情况,为了达到这一点,考虑到两次异或同一个数会抵消的性质,显然特征值回是树上的一段路径边权异或和。一个想法是钦定一个根后,一个点的点权为这个点到根的路径的异或和。容易发现如果对于一条不连接根的边执行操作,其效果相当于交换这两个点的点权。
但是如果对一条连接根的边执行操作,情况为比较复杂。为此可以选择一个度数为 \(1\) 的点作为根,那么这样的边就只有一条。
如果全程不选择这条边,那么问题转化为有长度为 \(n-1\) 的序列,可以任意打乱顺序,问能否做到和另一个长度为 \(n-1\) 的序列相等。这样的话直接把序列变为(可重)集合,判断集合是否相等即可。
考虑如果选中这一条边(设它连接了 \(rt\) 和 \(u\),边权为 \(w\),所以 \(u\) 的点权也就是 \(w\))做操作,效果是让集合中除了 \(w\) 之外的所有数都异或上 \(w\)。
又考虑到可以通过交换,把任何一个点权换到 \(u\) 的位置上,所以问题转化为:
有一个大小为 \(n-1\) 的(可重)集合 \(S\),执行任意次操作,每次选择 \(a\in S\),然后令 \(S\leftarrow \{x\oplus a|x\in S, x\ne a\}\cup\{a\}\)。问能否变成另外一个集合 \(T\)。
注意一个重要性质:\(S\) 经过两次不重复操作能变成的集合都能通过一次操作代替(你可以模拟一下对两个不同的数依次执行操作的效果,它和一次操作完全一样),所以实际上只需要执行一次操作就足够了。
因为每次操作会改变 \(n-2\) 个数,即会改变奇数个数,因此 \(\bigoplus_{x\in S}x\) 会异或上 \(a\)。这样就可以计算出 \(a\)。如果 \(a\notin S\),则无解,否则模拟一下操作,然后判断集合是否相等。
时间复杂度 \(O(n\log n)\)。
评析
关键是要想到利用特征值的变化来反映复杂的,难以刻画的变化,比如本题中点权刻画边权。这算是一个经典技巧。
那如何想到设置点权为到根的路径边权异或和?其实这类特征值的设计方案不多,直接枚举即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, b) for(ll i=a;i<=b;i++)
#define rrep(i, a, b) for(ll i=a;i>=b;i--)
const ll N=1e5+9;
ll n;
vector <ll> to[N], len1[N], len2[N];
ll d1[N], d2[N];
void dfs(ll u, ll fa){
for(ll i=0;i<to[u].size();i++){
ll v=to[u][i];
if(v==fa)continue;
d1[v]=d1[u]^len1[u][i];
d2[v]=d2[u]^len2[u][i];
dfs(v, u);
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin >> n;
rep(i, 1, n-1){
ll u, v, w1, w2;
cin >>u >> v >> w1 >> w2;
to[u].emplace_back(v);to[v].emplace_back(u);
len1[u].emplace_back(w1);len2[u].emplace_back(w2);
len1[v].emplace_back(w1);len2[v].emplace_back(w2);
}
ll rt=0;
rep(i, 1, n)if(to[i].size()==1){
rt=i;break;
}
dfs(rt, 0);
ll w=0;
rep(i, 1, n){
w^=d1[i]^d2[i];
}
rep(i, 1, n){
if(d1[i]==w){
rep(j, 1, n)if(j!=i&&j!=rt){
d1[j]^=w;
}
sort(d1+1, d1+n+1);sort(d2+1, d2+n+1);
rep(i, 1, n)if(d1[i]!=d2[i]){cout << "No" << endl;return 0;}
cout << "Yes" << endl;return 0;
}
}
cout << "No" << endl;
return 0;
}

浙公网安备 33010602011771号