poj1741 Tree

 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]≤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 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值