Tree POJ - 1741(点分治)

版权声明:欢迎大家指正错误,有不同观点的欢迎评论,共同进步 https://blog.csdn.net/Sirius_han/article/details/81986110

Tree

题目链接:POJ - 1741

题意:一颗无根树,已知树上的每条边及其权值,求出满足两点之间距离不超过k的点对的个数;

思路:假设根为r, 对于i, j两点之间的路径有两种情况:

1:经过根节点,此时i, j间的距离为dis[i][r]+dis[j][r];

2:不经过根节点,此时i, j两点必定在同一棵子树上,i, j间的距离就是dis[i][r.son]+dis[j][r.son];

上诉过程不就是一个递归的过程吗!

首先会想到所有两点间距离求出来,对符合条件的计数;但是这样是O(n^2)的复杂度;所以还需优化;

假设有下图所示一棵树:

当k=7时:

各点到达根节点1的距离分别为:dis[1]=0, dis[2]=1, dis[5]=2, dis[3]=2, dis[7]=4, dis[8]=5, dis[4]=3, dis[9]=6, dis[10]=7;

扫描二维码关注公众号,回复: 2975121 查看本文章

对其排序后为:

由上面信息我们可以找到的路径经由1的符合条件的点对为:8+6+4+3+1=22对;

怎么算的呢?用两个指针i, j分别指向1, 9(上方编号),如果下方对应的dis相加<=k就表明由i+1~j的所有点与i对应点经过1

后的路径小于等于k,就找到了j-i个符合题意的点对, 然后将i右移;反之将j左移,继续计算,直到i>=j;

但是这样计算也是存在问题的拿(2, 5)这个点对来说,其距离的路径不经过1,这样就多计算了,因为(2, 5)这一点对,应在计算路径经由2的符合条件的点对时计算,那么怎么去重呢?把dis[2][2]设为dis[1][2]在计算一遍路径经由2的点对,减去这个值就可以了;那么对于这样的方法复杂度为O(logn  *   nlogn), 当然了,这是最好情况(树的深度最小)下的计算,但是当树是一条链,而又由链头开始计算时复杂度就变成了O(n * nlogn);显然这样的复杂度无法接受了;此时就用到了点分治了;将树尽可能的压缩;

我对于点分治的理解就是选取一个合适的点将树的深度压缩,宽度增加;那怎样的点才满足呢?

没错就是树的重心;以下来自百度百科:

的重心也叫的质心。对于一棵树n个节点的无根树,找到一个点,使得把树变成以该点为根的有根树时,最大子树的结点数最小。换句话说,删除这个点后最大连通块(一定是树)的结点数最小。

所以现在的问题就是找树的重心;

其实是一个模板,固定套路;

首先计算出以某一点的子树的大小(节点数);永远取子树大小和剩余节点数两个值中最大值最小的那么一个点为重心;

关于点分治可以看《分治算法在树的路径问题中的应用》这篇论文;

#include <stdio.h>
#include <string.h>
#include <algorithm>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn=10100;
int n, k;
struct node{
	int v, nxt, w;
}edge[maxn<<1];
int head[maxn], cnt, vis[maxn];
void add(int u, int v, int w){
	edge[cnt]=node{v, head[u], w};
	head[u]=cnt++;
}
//计算重心;
int r, _size[maxn], allnode, f[maxn];
void getroot(int u, int fa){
	_size[u]=1, f[u]=0;
	for(int i=head[u]; i!=-1; i=edge[i].nxt){
		int v=edge[i].v, w=edge[i].w;
		if(v==fa || vis[v]) continue;
		getroot(v, u);
		_size[u]+=_size[v];
		f[u]=max(_size[v], f[u]);
	}
	f[u]=max(allnode-_size[u], f[u]);
	if(f[u]<f[r]) r=u;
}
//计算各节点到根的距离;
int px[maxn], dis[maxn];
void getdis(int u, int fa){
	px[++px[0]]=dis[u];
	for(int i=head[u]; i!=-1; i=edge[i].nxt){
		int v=edge[i].v, w=edge[i].w;
		if(v==fa || vis[v]) continue;
		dis[v]=dis[u]+w;
		getdis(v, u);
	}
}
//计算以u为根的子树的贡献值, x表示u到u的距离;
int cal(int u, int x){
	dis[u]=x;px[0]=0;
	getdis(u, u);
	sort(px+1, px+px[0]+1);
	int temp=0;
	for(int i=1, j=px[0]; i<j; ){
		if(px[i]+px[j]<=k) temp+=j-i++;
		else j--;
	}
	return temp;
}
//计算以u为根的答案;
int ans;
void solve(int u){
	vis[u]=1;
	ans+=cal(u, 0);
	for(int i=head[u]; i!=-1; i=edge[i].nxt){
		int v=edge[i].v, w=edge[i].w;
		if(vis[v]) continue;
		ans-=cal(v, w);
		r=0;
		allnode=_size[v];
		getroot(v, u);
		solve(r);
	}
}
int main(){
	while(scanf("%d%d", &n, &k), n||k){
		cnt=0;
		memset(head, -1, sizeof(head));
		memset(vis, 0, sizeof(vis));
		for(int i=1; i<n; i++){
			int u, v, w;
			scanf("%d%d%d", &u, &v, &w);
			add(u, v, w);
			add(v, u, w);
		}
		r=ans=0;
		allnode=n;
		f[0]=INF;
		getroot(1, 1);
		solve(r);
		printf("%d\n", ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Sirius_han/article/details/81986110
今日推荐