靜態點分治學習筆記
阿新 • • 發佈:2018-11-12
- 處理樹上路徑問題。
填以前的坑。
靜態點分治。
- 點分治的核心思想就是分治,每次選取樹的重心把樹分成兩個部分。
- 在這裡,樹的重心的定義是指以他為根,最大子樹\(sz\)最小。
- 然後每次劃分就只考慮經過樹的重心的路徑。
因為每次劃分都至少把樹分成一半,所以複雜度就是\(log\)了。
- 得到重心:
void Getroot(R i,R fm){ sz[i]=1;R num=0; for(R k=hd[i];k;k=nt[k]){ if(to[k]==fm||vis[to[k]])continue; Getroot(to[k],i),sz[i]+=sz[to[k]]; num=max(num,sz[to[k]]); } num=max(num,tot-sz[i]); if(num<Max)Max=num,root=i; }
- 注意這裡的重心是劃分出來的一個樹,應該可以理解成一個聯通塊。
- \(vis\)就是已經劃分過的割點,不能走了。
- \(num\)表示最大的子樹,\(tot\)是這個子樹大小。
num=max(num,tot-sz[i]);
- 注意到這一句,這個以這個點為根的子樹最大值,是本來的子樹大小,和聯通塊大小減去子樹大小的最大值。
- 然後所有的點取最小的點即可。
第一種寫法:
- 跳點分治樹:
void Dfs(R i,R fm){ sol(i,0,1),vis[i]=1; for(R k=hd[i];k;k=nt[k]){ if(to[k]==fm||vis[to[k]])continue; sol(to[k],w[k],-1),tot=sz[to[k]],Max=inf; Getroot(to[k],0),Dfs(root,0); } }
- \(sol\)的含義就是求解答案,每個題目不一樣。
- 因為現在的\(i\)一定是重心,\(vis[i]=1\)就相當於把樹隔開了。
然後列舉所有的兒子,再把一個兒子中的貢獻減去。
注意:
- 我們考慮的是經過重心的路徑,但是可以發現,對於一個重心\(i\),兩個點在同一個子樹內,那麼路徑是不會經過\(i\)的。然後我們單獨把這個子樹內的答案剪掉。注意,此時每個點的初始長度都是\(w_k\),這樣才可以正確剪掉原來被重複統計的路徑。
- 然後初始化\(tot\),\(Max\),得到每一個子樹根,然後再跳點分樹。
- 每一次統計答案可能要清空。
第二種寫法:
- 對於最大值/最小值,不好刪去重複的貢獻(方案類問題是很好刪除的,就是直接剪掉即可,但是最大值你不好刪掉,因為不好維護次大值。)
- 跳點分樹
void Dfs(R i){
vis[i]=1;
for(R k=hd[i];k;k=nt[k])
if(!vis[to[k]])go(to[k],i,1,w[k]),let(to[k],i,1,w[k]);
for(R k=hd[i];k;k=nt[k])
if(!vis[to[k]])emp(to[k],i,w[k]);
for(R k=hd[i];k;k=nt[k]){
if(vis[to[k]])continue;
tot=sz[to[k]],rt=0,Mx=n+1;
Getrt(to[k],0),Dfs(rt);
}
}
- 同樣的,把樹隔開。
- 然後對於每一個子樹,先考慮他和之前的點兩兩組合得到的答案(\(go\)函式)
- 然後在儲存這個子樹中一個答案產生的貢獻(\(let\)函式)
- 最後清除筒(\(emp\)函式)
- 然後再分重心。
- 例題:P4149 [IOI2011]Race
題單:
- [x] 點分治1
- [x] Tree
- [x] [國家集訓隊]聰聰可可
- [x] [IOI2011]Race
- 前面四道都是模板題。
- [ ] [Luogu2664]樹上游戲
- [ ] [WC2010]重建計劃
- [ ] [HDU4812]D Tree
- [ ] [SPOJ1825]Free tour II
- [ ] [HDU5977]Garden of Eden
- [ ] [HDU5909]Tree Cutting
- [ ] [[Luogu3727]曼哈頓計劃E]]
- [ ] P2993 最短路徑樹問題