【點分治總結】

點分治教程

例題
給定一棵帶權樹,顯然共有N*(N-1)/2條邊,問:第k小的邊邊長多長?
N<=10000.

題解:
這道題直接上手做實在是太難了,需要逐步拆分。
首先,問題是第k小的邊的邊長,這個問題不好解,但是轉換一下問題,二分第k小的邊長T,然後判斷這棵樹中<=T的路徑有多少條,這個能稍微好做一下,至少變成了一個統計性問題。
二分後題目變成:
給定一棵帶權樹,顯然共有N*(N-1)/2條邊,問:<=T的邊有多少條(POJ1741)。
顯然的,對於整個樹的根來說,路徑只有兩種:
1.經過根的路徑
2.不經過根的路徑
顯然,不經過根的所有路徑,都單獨屬於某個根的子樹中。

因此,我們完全可以這麼做此題:
void solve(int i)//表示統計以i爲根的這棵樹中的滿足題目要求的路徑個數。
{
work(i); //統計所有經過根節點i的滿足要求的路徑個數。
delete(i); //把節點i從整棵樹中刪掉,這樣i的所有兒子就形成了不同的子樹
for (i的任何一個兒子j)
{
if (j屬於i的某個子樹中)
solve(j); //統計以j爲根節點的子樹中滿足要求的路徑個數
}
}

例如:共有一顆樹,聯通情況如下:
1
2————|————3
4——|——5 6——|——7
那麼,首先調用solve(1),處理所有經過1的路徑,然後把1刪掉,調用solve(2),solve(3)分別處理,solve(2)處理完後調用了solve(4),solve(5),solve(3)以此類推。
這麼做顯然是明智的,把一個規模爲n的問題,通過計算一個比較簡單的子問題,轉換成了兩個規模爲2n的問題。
如果計算這個比較簡單的子問題的複雜度是nlogn的話,那麼總的複雜度就是nlognlogn。
然而,這麼做有一個反例:
1——2——3——4——5——6——7
首先調用solve(1),然後調用solve(2),然後調用solve(3)…
如此一來複雜度退化成了n*nlogn

爲了避免這一種退化,我們可以人爲的規定每棵子樹的根,對於上述問題:
首先調用solve(4),然後兩顆子樹就是1——2——3,5——6——7,再分別調用solve(2),solve(6)。
這樣,最多經過logn層,勢必把所有的節點都處理完畢了。

而每棵子樹的根很好選擇,顯然就是當前子樹的重心!用兩遍dfs可以求得。

solve的代碼非常好寫,如下:
void solve(int now)  //表示solve以now爲根的子樹,此時now已經是重心
{  
    int u;  
    ans += work(now);  //work(now)返回的是經過now的路徑中,滿足題意的數量。
    done[now] = true;  //把now從樹中刪除
    for (int i=0; i<g[now].size(); i++) //枚舉和now相鄰的每一個點u 
        if (!done[u = g[now][i].v])  //如果u沒有被刪除,說明在某一棵子樹中。
        {  
            f[0] = size = s[u];  
            getroot(u, root=0);  //找到u所在子樹的重心root 
            solve(root);  //遞歸處理root
        }  
}  

不會求樹重心的移步這裏

http://blog.csdn.net/xdu_truth/article/details/9104629

現在的問題變成了如何寫work(now),即在now所在的子樹中,有多少條經過now的邊長度<=T。
爲了求這個問題,我們可以求出子樹中now到所有點j的
路徑長度dis[j],
從誰來 from[j](表示j是從now編號爲from[j]的兒子走來的,例如1-2-3-4,from[4]=from[3]=from[2]=2)
那麼,問題就變成了,對於每一個dis[j],有多少個dis[t]+dis[j]<=T,且from[j]!=from[t],方法非常多,此處不再贅述。

作業:
poj1741,hdu4812,codeforces161D,bzoj3697,bzoj2152。

1.求數的重心:

//sz[x]-->x的樹大小,f[x]-->x最大子樹的節點數;

void getrt(int x,int fa)//利用*sz,*f求重心
{
    sz[x]=1;
    f[x]=0;
    for(int i=0;i<lin[x].size();i++)
    {
        int u=lin[x][i].x;
        if(vis[u]||u==fa) continue;
        getrt(u,x);
        sz[x]+=sz[u];
        f[x]=max(sz[u],f[x]);
    }
    f[x]=max(f[x],size-sz[x]);//!  x最大子樹的節點數=max(與此子樹大小-f[x],f[x])
    if(f[x]<f[rt]) rt=x; 
}

2.solve函數

void solve(int x)
{
    vis[x]=1;
    cal(x);//每到題不同的地方。。。。
    for(int i=0;i<lin[x].size();i++)
    {
        int u=lin[x][i].x;
        if(vis[u]) continue;
        f[0]=size=sz[u];
        getrt(u,rt=0);
        solve(rt);
    }

}

3**.接下來的cal函數**

//一般的兩個輔助數組
//pre[x] 代表到當前根的子樹時,當前子樹之前路徑上值爲x的方案數   
//now[x]  代表到當前根的子樹時,當前子樹上路徑上值爲x的方案數   
//dis[x]
void cal(int x)
{

    for(int i=0;i<lin[x].size();i++)
    {
        int u=lin[x][i].x;
        dis[u]=lin[x][i].y;
        if(vis[u]) continue;
        dfs(u,x);//dfs更新子樹的now【】值
        //ans1=。。。。。。。g[j]*ff[rev(j)]; 
         //統計數答案數;
   }
    for(int i=0;i<3;i++)  g[i]=0;

}

4.cal中的dfs

void getdis(int x,int fa)//一遍dfs求值+求重心的一點預處理sz[x],求子樹大小size
{
    sz[x]=1;
    d.push_back(dis[x]);
    for(int i=0;i<lin[x].size();i++)
    {
        int u=lin[x][i].x;
        if(vis[u]||u==fa) continue;     
       // dis[u]=dis[x]+lin[x][i].y;
        getdis(u,x);
        sz[x]+=sz[u];       
    }
}

例1:poj 1741
http://blog.csdn.net/alps233/article/details/51392495
nlog n 計算對於每個子樹的經過該子樹的根的方案數;
即 求出子樹中now到所有點j的
路徑長度dis[j],*logn 的排序統計;.cal(x)統計答案,x子樹中滿足dis[x]+dix[y]<=K 的方案數;

**例2:**codeforces 161D] Distance in Tree
http://blog.csdn.net/alps233/article/details/51393322
在cal函數中引入 tmp[],cnt[],代表作到x子樹時,當前層solve中之前子樹《=k的dis個數,因爲k<=500;O(n)計算,且不重複

例3: [bzoj 2152] 聰聰可可
http://blog.csdn.net/alps233/article/details/51396309
明顯的樹上的點分治,利用兩個數組g[]表示搜當前根的子樹時,當前子樹之前的路徑長x的方案數,ff表示當前子樹路徑長x方案數
*ans+=g[j]*ff[(3-j)%3]*2; //注意(1,1)合法,(1,2)(2,1)算兩種;

例4:hdu4812
預處理逆元后cal中處理flag【ni【i】】的位置進行更新

 if (path[j]*val[rt]%mod==K) getans(rt,id[j]);
            int tmp=K*ni[path[j]*val[rt]%mod]%mod;
            if(flag[tmp]!=ca) continue;
            getans(F[tmp],id[j]);

例5
bzoj3697: 採藥人的路徑
枚舉根節點的每個子樹。用f[i][0…1],g[i][0…1]分別表示前面幾個子樹以及當前子樹和爲i的路徑數目,0和1用於區分路徑上是否存在前綴和爲i的節點。

ans+=f[0][0] * g[0][0] + f [i][0] * g [-i][1] + f[i][1] * g[-i][0] + f[i][1] * g[-i][1]
//g[x][]代表到當前根的子樹時,當前子樹之前路徑上值爲x的方案數  0代表路徑上有休息點,1代表無休息點的個數;
//f[x][]  代表到當前根的子樹時,當前子樹上路徑上值爲x的方案數  0代表路徑上有休息點,1代表無休息點的個數;

其中i的範圍[-d,d],d爲當前子樹的深度。
在每個子樹 處更新並統計經過rt的方案數即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章