G. Yasya and the Mysterious Tree
题意
给定一颗
n
n
n 个节点的树,每条边有一个初始的权值
现在定义两种操作:
- ^ y \; y y,给所有的边的权值异或上 y y y
- ? u x u \; x ux,在树上任选一个不等于 u u u 的点,在其与 u u u 之间连接一条边权为 x x x 的边,这样构成了一个环,这个环的权值就是环的所有边权的异或和。输出所有方案的环的权值最大值
注意操作二并不会真的连边,只是假设连边,树始终是一颗树。
只需要对所有操作二回答答案即可
思路
我们先以
1
1
1 为根,在树上做
d
f
s
dfs
dfs,将每个点到根节点的异或和记录下来,记为
W
u
W_u
Wu
那么其实这是一个树上前缀异或和
进一步观察不难发现:操作一既然是对所有边都异或,那么我们并不需要真的对所有边都异或,我们只需要记录所有的操作一的
y
y
y 异或起来(记为
X
X
X ),然后根据操作二我们选择的点
v
v
v,其与点
u
u
u 之间经过的边的数量的奇偶性,就可以得出这条路径被操作一影响的权值。
如果长度为偶数,那么操作一等价于没操作;如果为奇数,那么只需要在环的最终权值异或上
X
X
X 即可。
到了这里不难想到要分奇偶讨论。先看长度为偶数的情况,即我们忽略操作一的影响,这时候我们只能选择距离
u
u
u 长度为偶数的点,我们可以先在一开始
d
f
s
dfs
dfs 的时候给每个点一个颜色,这样子就可以黑白染色,根据颜色来判断路径长度奇偶性了。
那么现在我们等价于只能选择与
u
u
u 颜色相同的点(长度为偶数),如果我们能快速求出
u
→
v
u \rarr v
u→v 的路径的异或和,然后异或上
x
x
x,就是这个环的权值了。
这里就到了让人眼前一亮的操作, u → v u \rarr v u→v 的路径异或和 d ( u , v ) = W u ⨁ W v d(u, v) = W_u \bigoplus W_v d(u,v)=Wu⨁Wv,即前面预处理的前缀异或和异或起来。
这是因为: d ( u , v ) = d ( u , l c a ( u , v ) ) ⨁ d ( l c a ( u , v ) , v ) d(u, v) = d\left(u, lca(u, v) \right) \bigoplus d\left(lca(u, v), v \right) d(u,v)=d(u,lca(u,v))⨁d(lca(u,v),v),那么它们 l c a ( u , v ) lca(u,v) lca(u,v) 往上的部分前缀异或和,由于异或了两次,所以被抵消了。
那么现在问题就转化为了:选择所有颜色与 u u u 相同的点,使得 W u ⨁ W v W_u \bigoplus W_v Wu⨁Wv 最大,这个就是很经典的 01 T r i e 01 \;Trie 01Trie 问题
同理,长度为奇数的情况就是选择与 u u u 颜色不同的点,使得 X ⨁ W u ⨁ W v X \bigoplus W_u \bigoplus W_v X⨁Wu⨁Wv 最大
还有一个细节就是,不能选择 u u u 本身,所以在查询之前,我们需要先将 u u u 删除,同时维护 T r i e Trie Trie 上的有效点即可,避免查询的时候误入歧途
时间复杂度: O ( ( n + m ) ⋅ log 1 0 9 ) O\left((n + m) \cdot \log 10^9 \right) O((n+m)⋅log109)
#include<bits/stdc++.h>
#define fore(i,l,r) for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
typedef long long ll;
const int N = 200005;
struct node{
int num[3]; //记录是否有效,用于删除自身
int clr;
int son[2];
}tree[N * 35];
int cnt;
std::vector<std::pair<int, int>> g[N];
std::pair<int, int> mes[N]; // [异或权值, 颜色]
void insert(int val, int c){ //c: 1黑 2白
int now = 0;
for(int i = 30; i >= 0; --i){
int ch = (val >> i & 1);
if(!tree[now].son[ch]) tree[now].son[ch] = ++cnt;
now = tree[now].son[ch];
tree[now].clr |= c;
++tree[now].num[c];
}
}
void dfs(int u, int fa, int sum, int c){
insert(sum, c);
mes[u] = {sum, c};
for(auto [v, w] : g[u])
if(v != fa)
dfs(v, u, sum ^ w, c ^ 3);
}
void erase(int val, int c){
int now = 0;
for(int i = 30; i >= 0; --i){
int ch = (val >> i & 1);
now = tree[now].son[ch];
--tree[now].num[c];
}
}
int query(int w, int c){
int res = 0;
int now = 0;
for(int i = 30; i >= 0; --i){
int val = (w >> i & 1);
int son1 = tree[now].son[1];
int son0 = tree[now].son[0];
if(!val){
if(son1 && (tree[son1].clr & c) && tree[son1].num[c]){
res |= 1 << i;
now = son1;
}
else now = son0;
}
else{
if(son0 && (tree[son0].clr & c) && tree[son0].num[c]){
res |= 1 << i;
now = son0;
}
else now = son1;
}
}
return res;
}
int main(){
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t;
std::cin >> t;
while(t--){
int n, q;
std::cin >> n >> q;
fore(i, 1, n){
int u, v, w;
std::cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs(1, 0, 0, 1);
int xor_sum = 0;
while(q--){
char opt;
std::cin >> opt;
if(opt == '^'){
int x;
std::cin >> x;
xor_sum ^= x;
}
else{
int v, x;
std::cin >> v >> x;
x ^= mes[v].fi;
erase(mes[v].fi, mes[v].se);
std::cout << std::max(query(x, mes[v].se), query(x ^ xor_sum, mes[v].se ^ 3)) << ' ';
insert(mes[v].fi, mes[v].se);
}
}
std::cout << endl;
fore(i, 0, cnt + 1){
tree[i].clr = 0;
tree[i].son[0] = tree[i].son[1] = 0;
tree[i].num[1] = tree[i].num[2] = 0;
}
cnt = 0;
fore(i, 1, n + 1) g[i].clear();
}
return 0;
}