1. 程式人生 > 實用技巧 >Yoi #320. 撕破傷口

Yoi #320. 撕破傷口

題面:

現在有一棵以1為根的樹,樹邊有權值,每個點上有一個人,他們會互相陰陽怪氣。

任意兩個人互相陰陽怪氣的程度定義為他們之間路徑的異或和。

你想用撕破傷口、讓他們互相更加陰陽怪氣,所以你現在想知道對於每條樹邊,經過它的路徑的最小陰陽怪氣程度。

對於所有資料:1≤n≤10^5^,0≤maxw<2^30^,且樹的形態隨機

分析:

考慮快速求出路徑的異或和,顯然預處理出每個端點到根的路徑的異或和,直接算,這是\(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;
}