Codeforces Round 950 G. Yasya and the Mysterious Tree 【树上异或和差分、01Trie】

G. Yasya and the Mysterious Tree

12

题意

给定一颗 n n n 个节点的树,每条边有一个初始的权值
现在定义两种操作:

  1. ^    y \; y y,给所有的边的权值异或上 y y y
  2. ? 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 uv 的路径的异或和,然后异或上 x x x,就是这个环的权值了。

这里就到了让人眼前一亮的操作, u → v u \rarr v uv 的路径异或和 d ( u , v ) = W u ⨁ W v d(u, v) = W_u \bigoplus W_v d(u,v)=WuWv,即前面预处理的前缀异或和异或起来。
这是因为: 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 WuWv 最大,这个就是很经典的 01    T r i e 01 \;Trie 01Trie 问题

同理,长度为奇数的情况就是选择与 u u u 颜色不同的点,使得 X ⨁ W u ⨁ W v X \bigoplus W_u \bigoplus W_v XWuWv 最大

还有一个细节就是,不能选择 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;
}
  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值