【点分治总结】

点分治教程

例题
给定一棵带权树,显然共有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//枚举和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;isize();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]

2.solve函数

void solve(int x)
{
    vis[x]=1;
    cal(x);//每到题不同的地方。。。。
    for(int i=0;isize();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;iint 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;iint 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的方案数即可

你可能感兴趣的:(树的点分治)