点分治模板【洛谷P3806】

传送门被藏起来啦!

这是个点分治模板题。
那么点分治是干什么用的呢?
当然就是解决这个问题的啦!

我们现在分析一下这个题目怎么解决。

询问有没有树上点对距离为k的点对,那么我们显然可以从每一个结点开始dfs,然后查看是否有距离为k的点对就可以了。但是,这个显然是不能解决这个问题的。因为复杂度很大。

我们来分析一下,对于树上的点对问题,可以归结为两类,

  1. 点对的路径经过某一个根节点
  2. 点对在某一棵树的子树中

那么我们就可以分治进行处理了。

首先处理一个根,然后处理其子树

如果我们的根选的’恰当’的话,这个问题的复杂度就会大大降低。
如果我们每次选的根都是当前树的重心,那么树的层数就会变成大致log级别,复杂度就会降到log级别了。

现在我们依次处理点分治的算法流程:

  1. 首先求树的重心(重心的定义是使树的左右子树的数目最大值最小的结点),我们可以使用dp来求重心,这个比较简单,下面会在代码中解释。
  2. 对于每一个重心,求出其子树的结点到重心的距离。这个直接dfs就可以求出来。
  3. 重心确定好了,距离求出来了,我们就可以统计答案了,统计答案不细说了,具体问题具体分析吧。
  4. 然后我们递归处理重心的子树。

下面放上代码,这次的代码里有了不少注释,相信大家可以看的懂的。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+7;
int sz[maxn],f[maxn],dis[maxn]; //分别表示树的大小,树的最大值,距离 
int vis[maxn];
int ans[100000010];  //离线记录答案 
int cnt = 0;
int sum = 0,rt = 0;
int n,m;
struct node
{
	int to;
	int cost;
};
vector<node> G[maxn];
void getrt(int now,int fa)  //求重心,选根 
{
	sz[now] = 1;
	f[now] = 0;
	for(int i=0;i<G[now].size();i++)
	{
		int v = G[now][i].to;
		if(v!=fa && !vis[v])
		{
			getrt(v,now);
			sz[now] += sz[v];
			f[now] = max(f[now],sz[v]);  //找子树中的最大值 
		}
	}
	f[now] = max(f[now],sum-sz[now]);  //一个结点可以把图分成上下两部分,我们更新一下上下两部分的最大值 
	if(f[now]<f[rt]) rt = now;  //我们要选的是最大值最小的,所以把根节点更新 
}
void getdis(int now,int fa,int len) //求结点到根节点的距离 
{
	dis[++cnt] = len;
	for(int i=0;i<G[now].size();i++)
	{
		int v = G[now][i].to;
		if(v!=fa && !vis[v])
		{
			getdis(v,now,len+G[now][i].cost);
		}
	}
}
void solve(int now,int len,int w) //记录答案 
{
	cnt = 0;
	getdis(now,0,len);
	for(int i=1;i<=cnt;i++)
	{
		for(int j=1;j<=cnt;j++)
		{
			if(i!=j) ans[dis[i]+dis[j]] += w; 
		}
	}
}
void divide(int now)  //点分治 
{
	vis[now] = 1;  //标记访问过now 
	solve(now,0,1);//加上个数 
	for(int i=0;i<G[now].size();i++)
	{
		int v = G[now][i].to;
		if(!vis[v])
		{
			solve(v,G[now][i].cost,-1); //减掉重复的个数,因为solve(now,0,1)会被计算两次 
			rt = 0; //下面就是重新找重心,选根结点 
			sum = sz[now];
			f[rt] = n;
			getrt(v,now);
			divide(rt); //处理子树 
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=0;i<n-1;i++)
	{
		int x,z,y;
		scanf("%d%d%d",&x,&y,&z);
		G[x].push_back({y,z});
		G[y].push_back({x,z});
	}
	sum = n;
	f[rt=0] = n;
	getrt(1,0);
	divide(rt);
	for(int i=0;i<m;i++)
	{
		int k;
		scanf("%d",&k);
		if(ans[k]) printf("AYE\n");
		else printf("NAY\n");
	}
	return 0;
} 

如果看不懂的话可以留言或者发邮件(cncaoyue@nuist.edu.cn)或者加Q(304960005)(加备注),我会尽力解答大家的疑惑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值