【POJ 1987】树的点分治 Distance Statistics

本文介绍了一道关于树分治的经典算法题目,旨在求解树上距离不超过特定阈值的节点对数目。通过找到树的重心并进行递归分解,最终实现高效计算。

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

Frustrated at the number of distance queries required to find a reasonable route for his cow marathon, FJ decides to ask queries from which he can learn more information. Specifically, he supplies an integer K (1 <= K <= 1,000,000,000) and wants to know how many pairs of farms lie at a distance at most K from each other (distance is measured in terms of the length of road required to travel from one farm to another). Please only count pairs of distinct farms (i.e. do not count pairs such as (farm #5, farm #5) in your answer). 
Input
* Lines 1 ..M+1: Same input format as in "Navigation Nightmare" 

* Line M+2: A single integer, K. 
Output
* Line 1: The number of pairs of farms that are at a distance of at most K from each-other. 
Sample Input
7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
10
Sample Output
5
Hint
There are 5 roads with length smaller or equal than 10, namely 1-4 (3), 4-7 (2), 1-7 (5), 3-5 (7) and 3-6 (9). 

    最近复习了分治,还学了一些以前没有学过的分治(像什么三分啊、树上的分治啊之类的),这道题就是一道典型的树分治(然而打脸的是做了2小时,TLE了10+次)。

    题意大概是说给出一个带权图(显然是棵树),求图中(树上)距离不超过k的节点对数,我们可以使用树分治来做。

    所谓树分治,就是在树上做分治,将问题转化为规模更小的几个问题,在子树中分别求解。为了让分解的规模更平均,我们必须找到树上的重心,因为这样才可以将子问题分得更小,方便我们求解(不TLE)。重心,就是将其拆掉后原图分成的几颗树中节点最多的树的节点数量在所有节点中最小的节点(好像有些绕)。

    画图说明吧!如下图,重心即为右图的根节点,因为当他被拆掉时,分出来的树最多有3个节点,而其他的都不能做到比他更少


    我们可以用一遍dfs求出一个重心,然后进行统计。

    此时,设我们已经找到重心r,那么距离小于k的节点对共有两种可能:1.他们之间跨越了r,2.他们之间并没有跨越r,第2种可以递归求解,所以我们只看第一种。将每个点到根节点的距离求出来,进行排序,然后线性寻找满足条件的,最后只要再减去来自同一子树的情况即可(因为他们肯定会在子树中再被计算)。

    当然,这样做的效率并不很高,要注意各种各样的细节处理以防像我一样TLE很多次……

    好了,不说了,放代码:(别在意我的注释)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
#include<cstdlib>
#include<cmath>
using namespace std;
#define N 40005
#define M 80005
int n;
int m;//花瓶×1
int fir[N],t[M],nex[M],cnt,son[N],most[N]={0x3f3f3f3f},root,tot;
int k,l[M],dis[N],dis2[N];
bool vis[N];
void add(int a,int b,int c)
{
	cnt++;
	t[cnt]=b;
	nex[cnt]=fir[a];
	l[cnt]=c;
	fir[a]=cnt;
}
void dfs(int r,int f)//重心...
{
	son[r]=1;most[r]=0;
	for(int i=fir[r];i;i=nex[i])
	{
		int v=t[i];
		if(v!=f&&!vis[v])
		{
			dfs(v,r);
			most[r]=max(most[r],son[v]);
			son[r]+=son[v];
		}
	}
	most[r]=max(n-son[r],most[r]);
	if(most[r]<most[root])
		root=r;
}
void GD(int r,int f)//距离重心的距离
{
	if(dis[r]<=k)dis2[++tot]=dis[r];
	else return;
	for(int i=fir[r];i;i=nex[i])
	{
		int v=t[i];
		if(v!=f&&!vis[v])
		{
			dis[v]=dis[r]+l[i];
			GD(v,r);
		}
	}
}
int num(int r)
{
	tot=0;GD(r,0);
	sort(dis2+1,dis2+tot+1);
	int zb=1,yb=tot,sum=0;
	while(zb<yb)
	{
		while(yb>zb&&dis2[zb]+dis2[yb]>k)
			yb--;
		sum+=yb-zb;
		zb++;
	}
	return sum;
}
int ans(int r)
{
	vis[r]=1;
	dis[r]=0;int sum=num(r);
	for(int i=fir[r];i;i=nex[i])
	{
		int v=t[i];
		if(!vis[v])
		{
			sum-=num(v);
			n=son[v];
			dfs(v,root=0);
			sum+=ans(root);
		}
	}
	/*for(int i=fir[r];i;i=nex[i])
	{
		int v=t[i];
		if(!vis[v])
		{
			n=son[v];
			dfs(v,root=0);
			sum+=ans(root);
		}
	}*/
	return sum;
}
int main()
{
	//freopen("1.txt","r",stdin);
	//int start=clock();
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int a,b;
		int c;
		char S[5];//花瓶×2
		scanf("%d%d%d%s",&a,&b,&c,S);
		add(a,b,c);
		add(b,a,c);
	}
	scanf("%d",&k);
	dfs(1,0);
	printf("%d",ans(root));
	//int End=clock();
	//printf("\n%d",End-start);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值