P3806 【模板】點分治1題解
題幹
給定一棵有n個點的樹,詢問樹上距離為k的點對是否存在。
分析
Part1:點分治的意義
分治都懂吧,將問題劃分為若干個子問題,最後合併答案
可以類比歸併排序,氣泡排序的複雜度為O(n^2),而歸併則是穩定的O(n log2 n)
點分治同理,我們將每個樹劃分為若干個子樹,然後分開來處理,這樣可以優化時間複雜度
Part2:如何點分治
觀察若有這樣一棵樹,若我們把1當做根節點,則點分治就毫無意義
因為這時候我們的遞迴層數為n層
時間複雜度仍然是O(n^2)
但如果看做這樣一棵樹呢?
顯然,若按照根節點分割開後,剩下的子樹為12,45每一顆樹的大小縮小為原來的一半
所以遞迴的層數直接降為 log2 n 層
可以得到現在的複雜度僅有O(n log2 n)
那麼我們怎麼找到要切割的這個點呢?
顯然是樹的重心,這樣分割完後剩下的子樹會盡量地平衡(size的最大會最小),從而達到最優的時間複雜度
Part 3:如何操作
對於一棵樹,該樹上的所有路徑必能分為:
1.經過根節點的路徑
2.沒經過根節點的路徑
情況1:
先考慮第一種情況:我們對每一個根(也就是重心),我們求取他所有的dis[k]表示k到根的距離
那麼任意兩點的距離(經過根)distance(x,y)=dis[x]+dis[y]
對於一個詢問k,若存在dis[x]和dis[y]=k-dis[x]則說明這個條件可以滿足
——————————————————————————————————————————————
但仍有一個例外:
假定我們以5為根節點(只畫出了左子樹嗷),設每一條邊長為1
若k=4,那麼4和1到5的距離加起來正好為4
但顯然1到4這條鏈上不會有5,所以我們還要統計該點是從那個(根節點的親兒子)來的
若兩個點從同一個兒子過來的,那麼就忽略這種情況
——————————————————————————————————————————————---
回到如何檢驗該詢問能否成立
我們將所有的dis從小到大排一個序,設定兩個座標1和dis的長度分別為l,r
若dis[l]+dis[r]>k,就將r減一,這樣總和就會變小
反之,將l加一,這樣總和就會變大
情況2:
如果該條鏈不經過根節點,那麼必然在他的子樹裡,我們就要進行分治
先將根節點斷開,對於每一棵子樹,我們先求得他的根節點也就是重心
之後我們仍然考慮兩種情況:
1.經過根節點的路徑
2.沒經過根節點的路徑
懂了嗎,情況一就和上面一樣,情況二則繼續分治
從而有了這樣一個divide函式:
void divide(int x) { vis[x]=1; calc(x); for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(vis[y]) continue; root=0; getrt(y,0,size[y]); divide(root); } }
vis陣列表示這個點已經被隔開來,calc函式用於解決在這個根節點上的情況一
之後對於他的每一個孩子,我們都要找到他的重心,然後繼續搜尋
下面貼一下calc函式,getrt函式,以及其附屬函式
calc:
void calc(int x) { tot=1; a[tot]=x; dis[x]=0; father[x]=x; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(vis[y]) continue; get(y,x,val[i],y); } sort(a+1,a+tot+1,cmp); for(int i=1; i<=m; i++) { int l=1,r=tot; if(ans[i]) continue; while(l<r) { if(dis[a[l]]+dis[a[r]]>query[i]) r--; else if(dis[a[l]]+dis[a[r]]<query[i]) l++; else if(father[a[l]]==father[a[r]]) { if(dis[a[r]]==dis[a[r-1]])r--; else l++; } else { ans[i]=1; break; } } } }
dis[i]表示所有點到i的距離
father[i]表示i對應的根的親兒子
get函式用於得到所有的dis和father
之後就按照我們上面的思路,排序,查詢,如果第i個詢問可行,則ans[i]=1
然後是get函式以及cmp函式:
bool cmp(int x,int y) {return dis[x]<dis[y];}
void get(int x,int fa,int now,int rt) { tot++; a[tot]=x; dis[x]=now; father[x]=rt; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(y==fa||vis[y]) continue; get(y,x,now+val[i],rt); } }
很明顯,不用過多的分析。
然後是getrt函式
void getrt(int x,int fa,int total) { size[x]=1; maxsum[x]=0; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(y==fa||vis[y]) continue; getrt(y,x,total); size[x]+=size[y]; maxsum[x]=max(size[y],maxsum[x]); } maxsum[x]=max(maxsum[x],total-size[x]); if(!root||maxsum[x]<maxsum[root]) root=x; }
依舊是很常規,maxsum[i]記錄i的最大子樹的大小,size記錄他子節點以及他本身的個數
然後就完美的解決遼~
程式碼:
void add(int a,int b,int c) { cnt++; nxt[cnt]=head[a]; to[cnt]=b; val[cnt]=c; head[a]=cnt; } void getrt(int x,int fa,int total) { size[x]=1; maxsum[x]=0; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(y==fa||vis[y]) continue; getrt(y,x,total); size[x]+=size[y]; maxsum[x]=max(size[y],maxsum[x]); } maxsum[x]=max(maxsum[x],total-size[x]); if(!root||maxsum[x]<maxsum[root]) root=x; } bool cmp(int x,int y) { return dis[x]<dis[y]; } void get(int x,int fa,int now,int rt) { tot++; a[tot]=x; dis[x]=now; father[x]=rt; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(y==fa||vis[y]) continue; get(y,x,now+val[i],rt); } } void calc(int x) { tot=1; a[tot]=x; dis[x]=0; father[x]=x; for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(vis[y]) continue; get(y,x,val[i],y); } sort(a+1,a+tot+1,cmp); for(int i=1; i<=m; i++) { int l=1,r=tot; if(ans[i]) continue; while(l<r) { if(dis[a[l]]+dis[a[r]]>query[i]) r--; else if(dis[a[l]]+dis[a[r]]<query[i]) l++; else if(father[a[l]]==father[a[r]]) { if(dis[a[r]]==dis[a[r-1]])r--; else l++; } else { ans[i]=1; break; } } } } void divide(int x) { vis[x]=1; calc(x); for(int i=head[x]; i; i=nxt[i]) { int y=to[i]; if(vis[y]) continue; root=0; getrt(y,0,size[y]); divide(root); } } int main() { ios::sync_with_stdio(false); cin>>n>>m; for(int i=1; i<n; i++) { int x,y,w; cin>>x>>y>>w; add(x,y,w); add(y,x,w); } for(int i=1; i<=m; i++) { cin>>query[i]; if(!query[i]) ans[i]=1; } maxsum[0]=n; getrt(1,0,n); divide(root); for(int i=1; i<=m; i++) if(ans[i]) cout<<"AYE"<<endl; else cout<<"NAY" <<endl; return 0; }View Code
query儲存讀入的k
ans用於記錄該詢問可不可行
在進入搜尋前先找一遍重心
AC~