点分治模板【洛谷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)(加备注),我会尽力解答大家的疑惑。

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值