BSOJ2764:树中点对统计 点分治

来源:互联网 发布:淘宝内裤真人秀图片 编辑:程序博客网 时间:2024/04/28 10:12
解释来自漆子超论文

2764 -- 【POJ1741】树中点对统计
Description
  给定一棵N(1<=N<=100000)个结点的带权树,每条边都有一个权值(为正整数,小于等于1001)。定义dis(u,v)为u,v两点间的最短路径长度,路径的长度定义为路径上所有边的权和。再给定一个K(1<=K<=10^9),如果对于不同的两个结点u,v,如果满足dist(u,v)<=K,则称(u,v)为合法点对。求合法点对个数。
Input
  输入文件的第一行包含两个整数n和k,接下来n-1行每行都有三个整数u,v,l, 表示结点u和结点v之间有一条长l的边。
Output
  输出文件只有一行为合法点对的个数。
Sample Input
  5 4
  1 2 3
  1 3 1
  1 4 2
  3 5 1
Sample Output
  8
Hint
【数据范围】:
  对于50%的数据,n<=1000,k<=1000;
  对于100%的数据,n<=100000,k<=10^9;
基于点的分治:首先选取一个点将无根树转为有根树,再递归处理每一颗以根结点的儿子为根的子树。
定理 1存在一个点使得分出的子树的结点个数均不大于N/2 

【算法分析】
如果使用普通的 DFS 遍历,时间复杂度高达ON2 ,而使用时间复杂度为O(NK)的动态规划,更是无法在规定时限内出解的。

我们知道一条路径要么过根结点,要么在一棵子树中,这启发了我们可以使用分治算法。

路径在子树中的情况只需递归处理即可,下面我们来分析如何处 理路径过根结点的情况。

 Depth(i)表示点 i 到根结点的路径长度,Belong(i)=X(X为根结点的某个儿子,且结点在以 X 为根的子树内)。那么我们要统计的就是:

满足 Depth(i)+Depthj) <=K Belong(i) !=Belong(j (ij)=满足 Depth(i)+Depth(j)<=(ij个数满足 Depth(i)+Depthj)<= Belong(i)=Belong(j(ij个数 而对于这两个部分,都是要求出满足 Ai+Aj<=k (ij的对数。将 A 排序后利用单调性我们很容易得出一个O(的算法,所以我们可以用O(log N的时间来解决这个问题。

综上,此题使用树的分治算法时间复杂度为Olog2


时间紧迫就只把word上的题解和注释版代码贴上来。

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#define size 400005using namespace std;struct Edge{int to,next,v;}w[size*4];int h[size]={0},cnt=0;int ans=0,k,n,d[size]={0};bool vis[size]={0};int Size[size]={0};void add(int x,int y,int v){cnt++;w[cnt].to=y;w[cnt].next=h[x];h[x]=cnt;w[cnt].v=v;}void init(){scanf("%d%d",&n,&k);for(int i=1;i<n;i++){int a,b,c;scanf("%d%d%d",&a,&b,&c);add(a,b,c);add(b,a,c);}}void DFS2(int x,int fa)//求Size[x]{Size[x]=1;for(int i=h[x];i;i=w[i].next){int to=w[i].to;if(!vis[to]&&to!=fa){DFS2(to,x);Size[x]+=Size[to];}}}int Findcenter(int x)//找树的重心当根{DFS2(x,0);int c=x,tot=Size[x];while(1){int cc=0;// cc ---> 未访问的size最大节点for(int i=h[c];i;i=w[i].next){int to=w[i].to;if(!vis[to]&&Size[to]>Size[cc])cc=to;}if(Size[cc]<=tot/2)break;//定理1else{Size[c]-=Size[cc];Size[cc]=tot;c=cc;                           //尝试把cc作为根,看是否符合。(注意改变c的Size值,不再是根) }}return c;//是return c,不是cc  -->分出子树(最大)节点数不超过N/2,则c为重心}void DFS3(int x,int fa,int dis)//找dis,当前节点到根的距离。{d[0]++;d[d[0]]=dis;for(int i=h[x];i;i=w[i].next){int to=w[i].to;if(!vis[to]&&to!=fa)DFS3(to,x,dis+w[i].v);}}void Calc(int x,int len,int cmd)//节点 , 初始到根距离 , cmd=1||-1 加还是减{d[0]=0;DFS3(x,0,len);sort(d+1,d+d[0]+1);//一定要sort..int L=1,R=d[0];while(L<R){if(d[L]+d[R]<=k)ans+=(R-L)*cmd,L++;else R--;}     //      L-->R都满足,则L-->R之前都满足,则多了R-L个点,这之后,L++,看L的下一层点是否符合,否则R--,看R的上层节点是否符合。    //为什么要cmd=-1,ans减去(R-L)呢?请往下看DFS函数,且谨记: 满足 Depth(i)+Depth( j) <=K且 Belong(i) !=Belong(j) 的 (i, j)个数=满足 Depth(i)+Depth(j)<=K 的(i, j) 个数–满足 Depth(i)+Depth( j)<=K 且 Belong(i)=Belong(j) 的(i, j) 个数。}void DFS(int x){int G=Findcenter(x);Calc(G,0,1);//所有的,不管belong的全部加上去。vis[G]=1;//只有这里要把vis改变为1。注意是vis[G]而不是vis[x],这里是针对G,对G的儿子来计算,所以是G被访问!for(int i=h[G];i;i=w[i].next)//从G开始邻接,删掉belong相同的{int to=w[i].to;if(!vis[to])Calc(to,w[i].v,-1),DFS(to);}}int main(){    init();    DFS(1);    printf("%d\n",ans);return 0;}



0 0
原创粉丝点击