1. 程式人生 > >靜態點分治學習筆記

靜態點分治學習筆記

  • 處理樹上路徑問題。
  • 填以前的坑。

    靜態點分治。

  • 點分治的核心思想就是分治,每次選取樹的重心把樹分成兩個部分。
  • 在這裡,樹的重心的定義是指以他為根,最大子樹\(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\),得到每一個子樹根,然後再跳點分樹。
  • 每一次統計答案可能要清空。
  • 例題 P2634 [國家集訓隊]聰聰可可

    第二種寫法:

  • 對於最大值/最小值,不好刪去重複的貢獻(方案類問題是很好刪除的,就是直接剪掉即可,但是最大值你不好刪掉,因為不好維護次大值。)
  • 跳點分樹
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

題單: