Poj1741 Tree 解题报告
Time Limit: 1000MS |
|
Memory Limit: 30000K |
Total 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 4
1 2 3
1 3 1
1 4 2
3 5 1
0 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]≤k且i、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 20005
using 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;
}