Yoi #320. 撕破伤口

题面:

现在有一棵以1为根的树,树边有权值,每个点上有一个人,他们会互相阴阳怪气。

任意两个人互相阴阳怪气的程度定义为他们之间路径的异或和。

你想用撕破伤口、让他们互相更加阴阳怪气,所以你现在想知道对于每条树边,经过它的路径的最小阴阳怪气程度。

对于所有数据:1≤n≤105,0≤maxw<230,且树的形态随机

分析:

考虑快速求出路径的异或和,显然预处理出每个端点到根的路径的异或和,直接算,这是\(O(n^2)\)

考虑用trie树优化

发现树的形态随机,这意味着树高是期望\(O(lgn)\)

可以对整棵树建立一棵trie树,在trie树上每次删掉通过的树边的一个端点为根的子树,然后对每个子树中的点算一遍

由于每个点只会在其父亲被算的时候删一遍,算一遍,而父亲只有\(O(lgn)\),时间复杂度为\(O(nlgn)\)

#include<bits/stdc++.h>
const int INF=2e9;
using namespace std;

const int N=1e5+5,M=3e6+5;
int rt,cnt,n,f[N],ans,to[N],nxt[N],he[N],w[N],ch[M][2],num[M];

inline void add(int u,int v,int k) {
    to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
    w[cnt]=k;
}

void ins(int p,int x,int y) {
    int u=1;
    for(int i=29;i>=0;i--) {
        int k=(x&(1<<i))!=0;
        if(!ch[u][k]) ch[u][k]=++cnt;
        u=ch[u][k];
        num[u]+=y;
    }
}

void dfs(int u) {
    for(int e=he[u];e;e=nxt[e]) {
        int v=to[e];
        f[v]=f[u]^w[e];
        dfs(v);
    }
    ins(rt,f[u],1);
}

vector<int>V;
void dgs(int u) {
    ins(rt,f[u],-1),V.push_back(u);
    for(int e=he[u];e;e=nxt[e]) {
        int v=to[e];
        dgs(v);
    }
}

int ask(int p,int x) {
    int u=p,sum=0; int ret=0;
    for(int i=29;i>=0;i--) {
        int k=(x&(1<<i))!=0;
        if(num[ch[u][k]]) u=ch[u][k];
            else u=ch[u][!k],ret|=(1<<i);
    }
//    printf("%d\n",ret);
    return ret;
}


int main() {
    freopen("wound.in","r",stdin);
    freopen("wound.out","w",stdout);
    scanf("%d",&n);
    for(int i=2;i<=n;i++) {
        int u,v,k; scanf("%d%d%d",&u,&v,&k);
        add(u,v,k);
    }
    rt=cnt=1; dfs(1);
//    for(int i=1;i<=n;i++) {
//        printf("%d\n",f[i]);    
//    }
    for(int i=2;i<=n;i++) {
        V.clear(),dgs(i);
        ans=INF;
        for(auto v:V){
            ans=min(ans,ask(rt,f[v]));
        }
        for(auto v:V){
            ins(rt,f[v],1);
        }
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2020-10-25 19:09  wwwsfff  阅读(106)  评论(0)    收藏  举报