点分治

来源:互联网 发布:网络推广外包合同 编辑:程序博客网 时间:2024/04/30 06:36

点分治

找到一棵树的重心,然后把这棵树拆开,这样就变成了另外几棵树,一直递归下去求解。

每次讨论三种情况:
1. 属于同一子树的顶点对(v,w),会在递归中被解决。
2. 属于不同子树的顶点对(v,w),要去掉和第一种重复的部分。
3. 重心s和其他顶点v组成的顶点对(s,v),可以巧妙地变为第二种。

例题 Poj 1741

给定一棵N(10^5)个顶点的树,边带正权,问你距离不超过k的顶点对数。

解析

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。
如果我们已经知道了此时所有点到根的距离a[i],a[x]+a[y]<=k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉。
因为我们每次都找树的重心,所以子树规模会很小。
时间复杂度O(nlog^2n)。

Q:如何找重心?
A:根据重心的定义,我们应该找到使最大子树顶点最少的点。

//找重心,最后重心是那个返回值的.secondtypedef pair<int,int> Point;Point calc_centroid(int U,int Fa,int nn){    Point res=make_pair(2147483647,-1);    int sum=1,maxv=0;    for(int i=first[U];i;i=next[i])      if(v[i]!=Fa&&(!centroid[v[i]]))        {          res=min(res,calc_centroid(v[i],U,nn));          maxv=max(maxv,size[v[i]]);          sum+=size[v[i]];        }    maxv=max(maxv,nn-sum);    res=min(res,make_pair(maxv,U));    return res;}

Q:如何去重?
A:我们可以把前面已经访问过的子树情况塞到一起,每次结束一棵子树时只考虑和前面的有多少种可能就好啦。

附上例题代码

#include<bits/stdc++.h>using namespace std;#define MAXN 10001typedef pair<int,int> Point;int n,K,ans;int v[MAXN<<1],w[MAXN<<1],first[MAXN],next[MAXN<<1],en;void AddEdge(const int &U,const int &V,const int &W){    v[++en]=V;    w[en]=W;    next[en]=first[U];    first[U]=en;}bool centroid[MAXN];//顶点是否已经作为重心删除的标记int size[MAXN];//以该顶点为根的子树的大小//计算子树的大小 int calc_sizes(int U,int Fa){    int res=1;    for(int i=first[U];i;i=next[i])      if(v[i]!=Fa&&(!centroid[v[i]]))        res+=calc_sizes(v[i],U);    return size[U]=res;}//查找重心的递归函数,nn是整个子树的大小//在以U为根的子树中寻找一个顶点,使得删除该顶点后得到的最大子树的顶点数最少//返回值为(最大子树的顶点数,顶点编号)Point calc_centroid(int U,int Fa,int nn){    Point res=make_pair(2147483647,-1);    int sum=1,maxv=0;    for(int i=first[U];i;i=next[i])      if(v[i]!=Fa&&(!centroid[v[i]]))        {          res=min(res,calc_centroid(v[i],U,nn));          maxv=max(maxv,size[v[i]]);          sum+=size[v[i]];        }    maxv=max(maxv,nn-sum);    res=min(res,make_pair(maxv,U));    return res;}int td[MAXN],en2,ds[MAXN],en3;//计算子树中所有顶点到重心的距离的递归函数void calc_dis(int U,int Fa,int d){    td[en2++]=d;    for(int i=first[U];i;i=next[i])      if(v[i]!=Fa&&(!centroid[v[i]]))        calc_dis(v[i],U,d+w[i]);}int calc_pairs(int dis[],int En){    int res=0;    sort(dis,dis+En);    for(int i=0;i<En;++i)      res+=upper_bound(dis+i+1,dis+En,K-dis[i])-(dis+i+1);    return res;}void solve(int U){    calc_sizes(U,-1);    int s=calc_centroid(U,-1,size[U]).second;    centroid[s]=1;    //情况1:递归统计按重心s分割后的子树中的对数    for(int i=first[s];i;i=next[i])      if(!centroid[v[i]])        solve(v[i]);    //情况2:统计经过重心s的对数    en3=0; ds[en3++]=0;    for(int i=first[s];i;i=next[i])      if(!centroid[v[i]]){          en2=0; calc_dis(v[i],s,w[i]);          ans-=calc_pairs(td,en2);//先把重复统计的部分(即情况1)减掉          memcpy(ds+en3,td,en2*sizeof(int)); en3+=en2;        }    ans+=calc_pairs(ds,en3);    centroid[s]=0;}void init(){    memset(first,0,sizeof(first));    en=ans=0;}int main(){    while(1){        scanf("%d%d",&n,&K);        if(!n&&!K) break;        init();        int a,b,c;        for(int i=1;i<n;++i){            scanf("%d%d%d",&a,&b,&c);            AddEdge(a,b,c); AddEdge(b,a,c);           }        solve(1);        printf("%d\n",ans);      }    return 0;}