POJ 1741 Tree

题意:给一颗n个顶点的树,求最短距离不超过k的顶点对数。

树的分治主要降低时间复杂度的地方就在于对树的分割,划分不均匀很容易使递归的深度退化为O(n),于是我们每次选择一个这样的点作为分割顶点,删除它后使得最大子树的顶点数最小,这样的顶点被称为重心。每次选择重心作为分割点,最大子树的顶点树必然不超过n/2,所以可以保证递归的深度是logN。

然后知道了树的分割后,所要求的顶点对就只有三种类型,第一种是两个点在同一子树上,第二种是一个点为分割点,另一个点在子树上,第三种是两个点分别在不同的子树上,由此分类可知此问题已可以用分治法求解。

第一种情况,可以通过递归得到答案

第二种情况和第三种情况一样,从顶点a到顶点b的路径必然包括分割点,求出每个顶点到分割点的距离,便可得解,对于第二种情况只需加一个与分割点距离为0的点便可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<limits.h>
using namespace std;
const int N = 10005, M = N*2;
int n,k,tot,minn;
int head[N],to[M],val[M],nxt[M];
int fa[N],siz[N],dep[N],l,r;
bool vis[N];

void add_edge(int s,int e,int v){
	to[tot]=e;val[tot]=v;
	nxt[tot]=head[s];head[s]=tot++;
}

int Cal_size(int s,int f){
	fa[s]=f;siz[s]=1;
	for(int i=head[s];~i;i=nxt[i])
		if(to[i]!=f && !vis[to[i]]) siz[s]+=Cal_size(to[i],s);
	return siz[s];
}

void Cal_root(int s,int sum,int &root){
	int maxx=sum-siz[s];
	for(int i=head[s];~i;i=nxt[i]){
		int e=to[i];
		if(e==fa[s] || vis[e]) continue;
		Cal_root(e,sum,root);
		maxx=max(maxx,siz[e]);
	}
	if(maxx<minn) minn=maxx,root=s;
}

void Find_path(int s,int d,int f){
	dep[r++]=d;
	for(int i=head[s];~i;i=nxt[i])
		if(to[i]!=f && !vis[to[i]])
			Find_path(to[i],d+val[i],s);
}

int Get_dep(int a,int b){
	sort(dep+a,dep+b);
	int ret=0,e=b-1;
	for(int i=a;i<b;++i){
		if(dep[i]>k) break;
		while(e>=a && dep[e]+dep[i]>k) --e;
		ret+=e-a+1;if(e>i) ret--;
	}
	return ret/2;
}

int Solve(int s){
	int sum=Cal_size(s,-1),ret=0,root;
	minn=INT_MAX;Cal_root(s,sum,root);vis[root]=1;
	for(int i=head[root];~i;i=nxt[i])
		if(to[i]!=root && !vis[to[i]])
			ret+=Solve(to[i]);
	l=r=0;
	for(int i=head[root];~i;i=nxt[i])
		if(to[i]!=root && !vis[to[i]]){
			Find_path(to[i],val[i],-1);
			ret-=Get_dep(l,r);
			l=r;
		}
	ret+=Get_dep(0,r);
	for(int i=0;i<r;++i)
		if(dep[i]>k) break;
		else ret++;
	vis[root]=0;
	return ret;
}

int main()
{
	int a,b,c;
	while(scanf("%d%d",&n,&k),n+k){
		tot=0;
		memset(head,-1,sizeof (head[0])*(n+1));
		memset(vis,false,sizeof (vis[0])*(n+1));
		for(int i=1;i<n;++i){
			scanf("%d%d%d",&a,&b,&c);
			add_edge(a,b,c);
			add_edge(b,a,c);
		}
		printf("%d\n",Solve(1));
	}
	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值