POJ 1741 Tree

本文介绍了一种使用重心分解解决特定带权树中合法点对计数问题的方法,并给出了详细的实现步骤与核心代码。合法点对是指两节点间距离不大于给定阈值k的节点对。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

此题的题意是:给定一棵N个结点的带权树,定义dist(v,u)为vu,两点间的最短路径长度,路径的长度定义为路径上所有边的权和。再给定一个k,如果对于不同的两个结点a,b,,如果满足dist(a,b) <= k , 则称(a,b)为合法点对。求合法点对数。

此题的思路主要是借鉴这篇论文:http://wenku.baidu.com/view/e087065f804d2b160b4ec0b5.html

看了论文之后也不是特别理解,然后又看了这篇代码http://www.cnblogs.com/andre0506/archive/2012/10/12/2721113.html,才理解了,不过处理的时候我个人觉得他有一点点错误,那就是在求拆掉一点能得到的最大数的点个数不是他所写的max(size[i], tp[s].sum-size[i]),而应该是max(size[i],tp[s].asum-tp[point[i]].asum);其中size[i]记录的是拆掉此点后以此点的儿子节点作为根节点的最大树的点个数。

以下为具体代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <algorithm>
#include <map>
#include <vector>
#include <stack>
using namespace std;
#define M 10005
#define ll long long
#define int64 __int64

struct node
{
	int ev, p;
	int asum , lsum;//sum代表以此点为根节点的整棵树节点个数,lsum为以此点的儿子节点为根节点的最多节点的树的节点个数
	node *next;
}*head[M], tree[2*M], tp[M];
int n, k, cnt, ans, tot, vis[M], size[M], point[M];

void init()
{
	memset(head, 0, sizeof head);
	memset(vis, 0, sizeof vis);
	cnt = ans = 0;
}

void addedge(int s, int e, int p)
{
	tree[cnt].ev = e;
	tree[cnt].p = p;
	tree[cnt].next = head[s];
	head[s] = &tree[cnt++];
}

void dfs(int s , int fa)
{
	tp[s].asum = 1;
	tp[s].lsum = 0;
	node *p = head[s];
	while (p)
	{
		if (p->ev != fa && !vis[p->ev])
		{
			dfs(p->ev,s);
			tp[s].asum += tp[p->ev].asum;
			tp[s].lsum = max(tp[p->ev].asum , tp[s].lsum);
		}
		p = p->next;
	}
	point[tot] = s;
	size[tot++] = tp[s].lsum;
}

int getroot(int s)
{
	tot = 0;
	dfs(s,0);
	int i , root , lsum = 1<<30;
	for (i = 0 ; i < tot ; i++)
	{
		size[i] = max(size[i],tp[s].asum-tp[point[i]].asum);//与上一篇代码的差别
		if (lsum > size[i])
		{
			lsum = size[i];
			root = point[i];
		}
	}
	return root;
}

void getdis(int s , int fa , int dis)//size重复利用,保留的是dist
{
	size[tot++] = dis;
	node *p = head[s];
	while (p)
	{
		if (p->ev != fa && !vis[p->ev] && dis+p->p <= k)
			getdis(p->ev , s , dis+p->p);
		p = p->next;
	}
}

void count1(int root)//计算所有经过根节点并且满足条件的路径的个数
{
	tot = 0;
	getdis(root , 0 , 0);
	sort(size , size+tot);
	int l = 0 , r = tot-1;
	while (l < r)
	{
		if (size[l]+size[r] <= k)
		{
			ans += r-l;
			l++;
		}
		else r--;
	}
}

void count2(int root)//删除节点在同一颗子树的情况
{
	vis[root] = 1;
	node *p = head[root];
	while (p)
	{
		if (!vis[p->ev])
		{
			tot = 0;
			getdis(p->ev , root , p->p);
			sort(size , size+tot);
			int l = 0 , r = tot-1;
			while (l < r)
			{
				if (size[l]+size[r] <= k)
				{
					ans -= r-l;
					l++;
				}
				else r--;
			}
		}
		p = p->next;
	}
}

void solve(int s , int fa)
{
	int root = getroot(s);
	
	count1(root);
	count2(root);

	node *p = head[root];
	while (p)
	{
		if (p->ev != fa && !vis[p->ev])
		{
			solve(p->ev,root);
		}
		p = p ->next;
	}
}

int main()
{
	while (scanf("%d%d",&n,&k) , n+k)
	{
		init();
		int i, s, e, p;
		for (i = 1 ; i < n ; i++)
		{
			scanf("%d%d%d",&s,&e,&p);
			addedge(s,e,p);
			addedge(e,s,p);
		}
		solve(1,0);//这里可以以任意一个点开始搜索,因为不论从哪个点开始,重心必然是一样的
		printf("%d\n",ans);
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值