树形dp之树的分治

【点的分治】

【POJ 1741 Tree】

已知一棵有n个点的树,以及每两个相邻点之间的距离,求两个的之间的最小距离<=k的点有多少对

可以先看一看论文《分治算法在树的路径问题中的应用 》

树的重心:删掉这个点之后,最大子树包含的节点最少

先找到重心,计算每一个点到重心的距离,用数组dis[ ]记录,我们要求的是点的对数,没必要知道是哪几个点

所以将dis[ ]排序,如果dis[i] + dis[j] <= k,i<r<j,那么dis[i] + dis[r] <= k,所以总对数ans+=(j-i)

如果一直按照这个顺序做下来,同一棵树内的两个点会算了好几遍,导致结果偏大

所以我们还要将同一棵树内的点对减去

用vector会超时

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
#define inf 11111
int n, k, ans, mi, root;
struct edge
{
	int v, next, w;
}edge[inf*5];
int head[inf], tot;
int size[inf], mx[inf], vis[inf];
void add(int u, int v, int w)
{
	edge[tot].v = v;
	edge[tot].w = w;
	edge[tot].next = head[u];
	head[u] = tot++;
}
void dfssize(int p, int u)//求的大小 
{
	size[u] = 1;
	mx[u] = 0;
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if(v != p && !vis[v])
		{
			dfssize(u, v);
			size[u]+=size[v];
			mx[u] = max(mx[u], size[v]);
		}
	}
}
void dfsroot(int p, int u, int r)//求重心 
{
	if(size[r] - size[u] > mx[u]) mx[u] = size[r] - size[u];
	if(mi > mx[u]) mi = mx[u], root = u;
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if(v!=p && !vis[v])
		{
			dfsroot(u, v, r);
		}
	}
}
int dis[inf], num;
void dfsdis(int u, int p, int d)//求树上的每一个点的重心的距离 
{
	dis[num++] = d;
	for(int i = head[u]; i != -1; i = edge[i].next)
	{
		int v = edge[i].v;
		if(v!=p && !vis[v])
		{
			dfsdis(v, u, d+edge[i].w);
		}
	}
}
int calc(int u, int d)//计算这个重心的点对 
{
	num = 0;
	dfsdis(u, 0, d);
	sort(dis, dis+num);
	int ret = 0;
	int l = 0, r = num-1;
	while(l < r)
	{
		while(dis[l]+dis[r]>k && l<r) r--;
		ret += r-l;
		l++;
	}
	return ret;
}
void dfs(int u)
{
	mi = n;
	dfssize(-1, u);
	dfsroot(-1, u, u);
	ans += calc(root, 0);
	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);
			dfs(v);
		}
	}
}
int main()
{
	while(~scanf("%d%d", &n, &k))
	{
		if(n==0 && k==0) break;
		memset(vis, 0, sizeof(vis));
		memset(head, -1, sizeof(head));
		tot=0;
		for(int i = 0; i < n-1; i++)
		{
			int a, b, d;
			scanf("%d%d%d", &a, &b, &d);
			add(a, b, d);
			add(b, a, d);
		}
		ans = 0;
		dfs(1);
		printf("%d\n", ans);
	}
	return 0;
}

【边的分治】


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值