POJ1741 Tree dp+树的点分治(楼天成推荐男人必做八题之一)

题目大意就是给一个无根树,让你找出这个书中两个点之间距离小于K的点对有多少对。。。。典型的树的点分治,无奈编码能力太弱,变了两天都没写好,太弱了,最后听说是楼教推荐的男人八题,我艹,男人不好当啊。。。。。。。刚开始搞树的分治,所以自己写的丑代码就不贴出来了,在网上找了一份精简的分享一下:具体树分治的知识参见漆子超09年的论文。。。。

将无根树转化成有根树进行观察。满足条件的点对有两种情况:两个点的路径横跨树根,两个点位于同一颗子树中。

如果我们已经知道了此时所有点到根的距离a[i],a[x] + a[y] <= k的(x, y)对数就是结果,这个可以通过排序之后O(n)的复杂度求出。然后根据分治的思想,分别对所有的儿子求一遍即可,但是这会出现重复的——当前情况下两个点位于一颗子树中,那么应该将其减掉(显然这两个点是满足题意的,为什么减掉呢?因为在对子树进行求解的时候,会重新计算)。

在进行分治时,为了避免树退化成一条链而导致时间复杂度变为O(N^2),每次都找树的重心,这样,所有的子树规模就会变的很小了。时间复杂度O(Nlog^2N)。

树的重心的算法可以线性求解。

 

 

 

/**********************
* author:crazy_石头
* Pro:POJ 1987
* algorithm:Tree dp+点分治
* Time:31ms
* Judge Status:Accepted
***********************/
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <vector>
#include <algorithm>

using namespace std;

#define rep(i,h,n) for(int i=(h);i<=(n);i++)
#define ms(a,b) memset((a),(b),sizeof(a))
#define eps 1e-6
#define INF 1<<29
#define LL __int64
const int N=20000+5;
const int maxm=200+10;

struct node
{
	int v, l;
	node() {};
	node(int _v, int _l): v(_v), l(_l) {};
};

vector<node> g[N];
int n, k, size, s[N], f[N], root, d[N], K, ans;

vector<int> dep;
bool done[N];

void getroot(int now, int fa)//找树的重心
{
	int v;
	s[now] = 1; f[now] = 0;
	for (int i=0; i<g[now].size(); i++)
		if ((v = g[now][i].v) != fa && !done[v])
		{
			getroot(v, now);
			s[now] += s[v];
			f[now] = max(f[now], s[v]);
		}
		f[now] = max(f[now], size-s[now]);
		if (f[now] < f[root]) root = now;
}

void getdep(int now, int fa)//找到 节点到根节点的距离
{
	int u;
	dep.push_back(d[now]);
	//s[now] = 1;//以now为根的节点数
	for (int i=0; i<g[now].size(); i++)
		if ((u = g[now][i].v) != fa && !done[u])
		{
			d[u] = d[now] + g[now][i].l;
			getdep(u, now);
			//s[now] += s[u];
		}
}

int calc(int now, int init)
{
	dep.clear(); d[now] = init;//表示now距离根节点距离(若now为根则init=0)
	getdep(now, 0);//以now为根建的子树
	sort(dep.begin(), dep.end());
	int ret = 0;
	for (int l=0, r=dep.size()-1; l<r; )
		if (dep[l] + dep[r] <= K) ret += r-l++;
		else r--;
		return ret;
}

void work(int now)
{
	int u;
	ans += calc(now, 0);
	done[now] = true;
	for (int i=0; i<g[now].size(); i++)
		if (!done[u = g[now][i].v])
		{
			ans -= calc(u, g[now][i].l);//这里建的树 是now的儿子u,距离根节点是dis(now,u)
			f[0] = size = s[u];
			getroot(u, root=0);
			work(root);
		}
}

int main()
{

	while (scanf("%d%d", &n, &K) == 2)
	{
		if (n == 0 && K == 0) break;
		for (int i=0; i<=n; i++) g[i].clear();
		memset(done, false, sizeof(done));
		int u, v, l;
		for (int i=1; i<n; i++)
		{
			scanf("%d%d%d", &u, &v, &l);
			g[u].push_back(node(v, l));
			g[v].push_back(node(u, l));
		}
		f[0] = size = n;
		getroot(1, root=0);
		ans = 0;
		work(root);
		printf("%d\n", ans);
	}
	return 0;
}

 

转自blog:http://blog.csdn.net/yang_7_46/article/details/9966455

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值