Yoi #320. 撕破傷口
阿新 • • 發佈:2020-10-25
題面:
現在有一棵以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; }