Tree POJ1741 树的分治 重心分解

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个节点的树,每条边有个权值代表相邻2个点的距离,要求求出所有距离不超过k的点对(u,v)

点分治算法的过程:

1)找出树的重心

①计算以u为根的树中每棵子树的大小

②根据子树大小找出树的重心root(以树的重心为根的树,可以使其根的子树中节点最多的子树的节点最少)

2)将树的重心作为根节点root,计算树中每个点到root的距离dir

3)计算树中所有满足dir[u]+dir[v]<=k的点对数cnt1

4)计算以root的子节点为根的子树中,满足dir[u]+dir[v]<=k的点对数cnt2

5)ans+=cnt1-cnt2

6)删掉节点root,分别遍历root的子树,回到第1)步

分析一下这个算法,我们每次都寻找到一个重心,把树较为平均地分为若干个子树。然后计算每个点到重心的距离,保存在dis数组里,然后用经典算法计算出和不超过k的组数。这些求出来满足小于等于K的里面只有那些路径经过重心的点对才是有效的,也就是说在同一颗子树上的肯定不算数的,因此要把它们减掉。

在这之后再对每一个子树找重心,求解,就可以求出所有的满足条件的顶点对了。

#include <iostream>
#include <string.h>
#include <algorithm>
#include <cstdio>
using namespace std;
const int maxn=11111, maxm=55555;

struct edge
{
	int v, next, w;
}edge[maxm];

int e, n, k,  ans, root, num, mi;
int head[maxm], dis[maxn], vis[maxn], size[maxn], mx[maxn];

void init(){
	memset(vis, 0, sizeof(vis));
	memset(head, -1, sizeof(head));
	e = ans = 0;
}

void add(int x, int y, int z){
	edge[e].v=y;
	edge[e].w=z;
	edge[e].next=head[x];
	head[x]=e++;
}

void dfssize(int u, int fa){         //处理子树的大小
	size[u] = 1;             //根为u的子树的大小
	mx[u] = 0;               //u的最大子树的大小
	for (int i = head[u]; i != -1; i = edge[i].next)
	{
		int v=edge[i].v;
		if (v != fa && !vis[v])    //结点v没有搜索过
		{
			dfssize(v, u);
			size[u] += size[v];
			if (size[v] > mx[u]) mx[u] = size[v];
		}
	}
}

void dfsroot(int r, int u, int fa){     //求重心
	if (size[r] - size[u] > mx[u]) 
            mx[u] = size[r] - size[u];   //u上面的树的点数比u的最大子树的点数大就更新mx[u]

	if (mx[u] < mi) mi = mx[u], root = u;      //最大子树最小的顶点就是重心

	for (int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if (v != fa && !vis[v]) dfsroot(r, v, u);
	}
}

void dfsdis(int u, int d, int fa)       //求各顶点到重心的距离
{
	dis[num++] = d;
	for (int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if (v != fa && !vis[v]) dfsdis(v, d + edge[i].w, u);
	}
}

int calc(int u, int d)
{
	int ret = 0;
	num = 0;
	dfsdis(u, d, 0);
	sort(dis, dis + num);     //求所有顶点中满足dis[i]+dis[j]<=k的组数
	int i = 0, j = num - 1;
	while (i < j)               //经典
	{
		while (dis[i] + dis[j] > k && i < j) j--;
		ret += j-i;
		i++;
	}
	return ret;
}

void dfs(int u){
	mi = n;
	dfssize(u, 0);           //求子树的大小
	dfsroot(u, u, 0);        //求重心
	ans += calc(root, 0);    //ans+=所有顶点中满足dis[i]+dis[j]<=k的组数
	vis[root] = 1;
	for (int i = head[root]; i != -1; i = edge[i].next)
 	{
 		int v = edge[i].v;
 		if (!vis[v])
 		{
 			ans -= calc(v, edge[i].w);  //减去子树中dis和小于等于k的组数
 			dfs(v);
 		}
 	}
}

int main()
{
	int x, y, z;
	while (scanf("%d%d", &n, &k)!=EOF){
	if (n==0 && k==0) break;
	init();
	for (int i = 1; i < n; i++)
	{
		scanf("%d%d%d", &x, &y, &z);
		add(x, y, z);
		add(y, x, z);
	}
	dfs(1);
	printf("%d\n", ans );
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41703679/article/details/81629605