就是把分治搬到樹上,以某個點為根,分別分治處理子樹的答案,再計算子樹與子樹間的答案的玄學演算法。
舉個例子:
如何求出一顆樹上距離為k且所經過的點最少的點對?
對於這種題,我們可以把某個點(一般為重心)作為根,然後對左右子樹遞迴處理,先分別得出左右子樹的答案,再求出橫跨兩個子樹之間的點對的答案。
對於上面那道題,如果我們用傳統的暴力做法,最優的複雜度只能得到$o(n^2)$的暴力列舉,但是如果我們用澱粉質來搞,我們就可以做到$o(n*log_n)$的複雜度(如果k很大的話,我們可能還得套個資料結構上去,複雜度就變成了$o(n*log_n^2)$),我們就可以通過這類題目。
正如我們剛剛說說的,澱粉質的實質是選出乙個點,對其左右子樹分治,再計算組合不同子樹的答案。
所以說,我們在處理某顆子樹之前,必須先選出來乙個點作為這個子樹的根。
我們選的這個點,對我們複雜度的影響極其巨大。
我們可以考慮選重心,重心就是指如果這個點做為根,其任意一顆子樹大小均不會超過總點樹的$1/2$。
如果我們每次對子樹分治的時候,新選的根是重心,那我們的樹的總層數一定不會超過$log_n$層。
對於找重心,我們每次都做一遍dfs就好。
int getsize(intnow)
introot,cnt;
void getroot(int
now)
if(cnt-t_size>cnt/2) ok=false
;
if(ok==true) root=now;
t_vis[now]=false;}
選出來重心之後,我們就可以以重心為根分治了。
首先,我們顯然可以遞迴處理子樹,然後再處理跨越兩個子樹的情況。
對於我們上面講的那道題,我們所需要求的就是跨過兩個子樹且距離為k的點對的數量。
我們可以考慮這樣做:
我們先開乙個桶來記錄長度為x的路徑的點的數量,然後對每顆子樹先做兩遍dfs,第一遍先去桶裡找到目前的點為止,之前找到的子樹中有沒有k-dis_now的路徑。第二遍我們再把到每個點的距離放到桶裡面去。
搞完之後,我們可以再dfs一次來回溯掉之前加上的東西,如果我們直接memset的話,會t的。
*注意:我們這裡的dfs必須只能走已經分治完成的點,分治完成的點才是其兒子
void dfs2(int now,int dis,int t_cnt,inttype)
void dfs(int
now)
for(int i=0;idfs2(son[i].to,son[i].w,
1,1),dfs2(son[i].to,son[i].w,1,2
);
for(int i=0;idfs2(son[i].to,son[i].w,
1,3);
done[now]=true;}
這樣子搞,看起來時間複雜度很**,其實並不大,因為我們最多有$log_n$層,每層做$n$次,總複雜度也就只有$o(n*log_n)$
這樣子,我們就搞定啦φ(>ω<*)
題目傳送門:題目稍有變動,但方法類似)
#include#include#include
#include
using
namespace
std;
long
long
read()
while(isdigit(c))
return x*f;
}const
int n=200000+100
;const
int m=1000000+100
;const
int inf=0x3f3f3f3f
;struct
road
};vector
e[n];
intn,k;
bool
vis[n],t_vis[n],done[n];
intsize[n],cnt,root;
int getsize(int
now)
void getroot(int
now)
if(cnt-t_size>(cnt/2
)) ok=false
;
if(ok==true) root=now;
t_vis[now]=false;}
int ans=inf,tot[m];
void dfs2(int now,int dis,int t_cnt,int
type)
void dfs(int
now)
for(int i=0;idfs2(son[i].to,son[i].w,
1,1),dfs2(son[i].to,son[i].w,1,2
);
for(int i=0;idfs2(son[i].to,son[i].w,
1,3);
done[now]=true;}
intmain()
cnt=getsize(1
); getroot(1);
memset(tot,
0x3f,sizeof
tot);
tot[
0]=0
; dfs(root);
if(ans>n) ans=-1
; printf("%d
",ans);
return0;
}
分治學習筆記
分治,即分而治之,將大問題分解為小問題,分別求解,最後合併結果。許多演算法都是建立在分治的基礎上的,比如說快速排序,歸併排序等 例題1 南蠻圖騰 不難發現每個圖案是由許多這個圖案組成的 然後就可以用分治來遞迴解決本題,別忘了處理空格喲 include using namespace std int ...
CDQ分治學習筆記
今天學了一下cdq分治,感覺這東西真的挺好用的,趕緊寫點東西怕以後再忘咯 其實類似於cdq分治的東西在oi早期學排序的時候就應該學過,那就是歸併排序 歸併排序的原理和cdq分治大體一樣,先劃分成兩個區間,遞迴解決兩邊,再合併起來 並且用歸併排序求逆序對的時候本質上就是在解決乙個二維偏序的問題 首先回...
點分治學習筆記
點分治主要用來處理樹上路徑問題,可以統計樹上點到點的所有路徑,複雜度o nlogn 基於樹上的結點進行分治,不斷將一棵樹拆成多顆子樹處理 選擇點時為了防止退化成鏈的情況,如果選點後左右子樹越大,遞迴層數越多,時間越慢,反之則越快,我們每次選擇子樹內的重心 void getroot int u,int...