poj1741 Tree

来源:互联网 发布:中国网络域名注册 编辑:程序博客网 时间:2024/06/14 06:30
 Poj1741 Tree 解题报告
Time Limit: 1000MS Memory Limit: 30000KTotal Submissions: 15559 Accepted: 5055

Description

Give a tree with n vertices,each edge has a length(positive integer less than 1001). 
Define dist(u,v)=The min distance between node u and v. 
Give an integer k,for every pair (u,v) of vertices is called valid if and only if dist(u,v) not exceed k. 
Write a program that will count how many pairs which are valid for a given tree. 

Input

The input contains several test cases. The first line of each test case contains two integers n, k. (n<=10000) The following n-1 lines each contains three integers u,v,l, which means there is an edge between node u and v of length l. 
The last test case is followed by two zeros. 

Output

For each test case output the answer on a single line.

Sample Input

5 41 2 31 3 11 4 23 5 10 0

Sample Output

8

题目大意

有一棵n个节点的树,定义dist(u,v)是点u到点v之间的距离,如果dist(u,v)≤k,则称点对(u,v)是一个合法点对,求整棵树上合法点对的数量。

分析

        ps:由样例知,点对(u,v)和点对(v,u)算作同一个点对。

        算法一:暴力

  先用dfs在n^2的时间复杂度内求出所有两个点之间的距离,然后枚举u、v,统计合法点对数量,需要开n*n的数组,时间复杂度O(n^2),空间复杂度O(n^2)

  算法二:树的点分治

  可以这样想:以1结点为根,求出所有经过这个节点和它的子树的路径,将符合条件的计入答案,再对每棵子树进行同样的操作,最终累加起来得到答案。
  点分治:设d[i]数组表示结点i到根节点的距离。枚举点i、j,如果d[i]+d[j]≤ki、j在不同的子树中,那么ans++。分析发现,对于每棵子树,都要先计算d数组(O(n)),再枚举i、j(O(n^2)),每个结点都要做一次的话,复杂度就是O(n^3),这显然不满足要求。时间都在枚举i、j是浪费掉了,于是考虑优化。有一种O(n)的扫描,如下:假设d数组是有序的,让i、j分别从左右端点向中间扫,明显地,如果d[i]+d[j]小于等于k,那么对于一切x<j,都有d[i]+d[x]≤k,所以扫描的过程如下:for(i=1,j=len,i<j;i++){while(i<j&&d[i]+d[j]>k)j--;ans+=j-i;},但这样统计出来的ans包含同一棵子树中的情况,我们可以在处理子树前先把这一部分减去,复杂度O(n),欲使d数组有序,还需要O(nlgon)的排序,因此处理每个点的复杂度降到了O(nlogn)
  用重心减少分治次数:根据上述过程,假设一颗子树有x个节点,那么处理的复杂度就是O(xlogx),所以总共的复杂度就是O((x1+x2+x3+...+xn)log(x1+x2+x3+...+xn)),对于一棵深度为y的满二叉树,复杂度是{y*2^y-[2^(y+1)-1]}(分析略),当节点数为n时,有y=log[(n+1)/2],就算logn吧,则复杂度大约是O[n×(logn)^2),这是非常快的。但是如果该树是一条链,则复杂度会降为O(n^2 * logn),这又是不能接受的了,一个很显然的做法就是把链每次从中间一分为二,再处理,复杂度有会成为(n(logn)^2)了。可是这是一条链,对于更普遍的情况呢?这里使用树的重心,树的重心是去掉这个节点后使得最大的子树包含节点数最少的点,每次以子树的重心为根节点进行分治,其时间复杂度就可以稳保在O[n(logn)^2]了。
  树的重心的找法:树的重心的性质是所有点到树的重心的距离和最小,我们可以先算出任意一个点x到所有节点的距离和,假设是sumx,对于其儿子y,设w为边(x,y)的权值,tot_node是整棵树的结点数,num[i]是以i为根的子树的结点个数,则有sumy=sumx-w*num[y]+w*(tot_num-num[y]),转移的复杂度是O(1)的,利用这一点从而我们可以在O(n)的时间复杂度内求出树的重心。
  一口气谢了不少。。。我之所以写的这么详细是因为在这之前我对树的分治一窍不通,我所写的都是我从这道题中学到的。
代码如下:
//poj1741 Tree 树的点分治#include <cstdio>#include <cstring>#include <algorithm>#define inf 0x3f3f3f3f#define maxn 10005#define maxm 20005using namespace std;int n, k, d[maxn], q[maxn], len, cnt[maxn], ans, head[maxn], next[maxm],w[maxm], to[maxm], tot, G, minsum, flag, tot_node;void add(int a, int b, int c){to[++tot]=b;w[tot]=c;next[tot]=head[a];head[a]=tot;}int count(){int i, j, s=0;sort(q+1,q+len+1);for(i=1,j=len;i<j;i++){while(i<j && q[i]+q[j]>k)j--;s+=j-i;}return s;}void dfs(const int x, const int f, const int dist){int p; if(flag==1){q[++len]=d[x];minsum+=dist;tot_node++;}if(flag==2)q[++len]=dist;d[x]=dist;cnt[x]=1;for(p=head[x];p;p=next[p]){if(to[p]==f)continue;dfs(to[p],x,dist+w[p]);cnt[x]+=cnt[to[p]];}}void findG(const int x, const int f, const int sumx){int p, t;for(p=head[x];p;p=next[p]){if(to[p]==f)continue;t=sumx-w[p]*cnt[to[p]]+(tot_node-cnt[to[p]])*w[p];if(t<minsum)minsum=t,G=to[p];findG(to[p],x,t);}}void deal(const int root, const int f){int p, g;if(to[head[root]]==f)head[root]=next[head[root]];else for(p=head[root];p;p=next[p])if(to[next[p]]==f){next[p]=next[next[p]];break;}len=minsum=0;flag=1;tot_node=0;dfs(root,f,0);ans-=count();G=root;findG(root,inf,minsum); flag=2;len=0;dfs(G,inf,0);ans+=count();g=G;for(p=head[g];p;p=next[p])deal(to[p],g);}int main(){int a, b, c, i;while(scanf("%d%d",&n,&k),n){tot=len=ans=0;memset(head,0,sizeof(head));memset(next,0,sizeof(next));memset(d,0x3f,sizeof(d));for(i=1;i<n;i++){scanf("%d%d%d",&a,&b,&c);add(a,b,c);add(b,a,c);}ans=0;deal(1,inf);printf("%d\n",ans);}return 0;}


0 0
原创粉丝点击