洛谷P3806 【模板】点分治1(点分治 模板题)

题目

给定一棵有n(n<=1e4)个点的树

询问树上距离为k(k<=1e7)的点对是否存在

询问m(m<=100)组

思路来源

https://www.cnblogs.com/bztMinamoto/p/9489473.html

https://baijiahao.baidu.com/s?id=1608747083240620752&wfr=spider&for=pc

https://www.cnblogs.com/PinkRabbit/p/8593080.html(点分治学习博客)

题解

入坑点分治,利用重心降复杂度

暂时只遇到解决树上路径的问题,

每次只统计必经过当前重心根的答案,

用在重心u的子树里任取的答案减去在儿子v的子树里任取的答案

每次操作分三步,不断递归执行①②③

①找重心

②确定重心到子树内各点的距离

③统计路径必经重心这个点的答案

点分治大部分是板子,主要改cal函数即可

这里重搜了m次,复杂度O(mnlogn)

注意在重搜之前对若干变量(如vis[])的初始化

还有多组数据对边的init()

 

存在cal函数里特判路径是否来自同一棵子树的做法,

开一个b[]数组来判点来自哪里,

用双指针判,判到路径必经根时再统计

 

后续:

发现这个题从1s改成400ms了,

原代码过不去了,毒瘤……

如何避开错误的点分治写法

思路来源:https://liu-cheng-ao.blog.uoj.ac/blog/2969

不能每次直接sz=siz[v],这样可能出现a是b的父亲,b是c的父亲,第一次以a为根dfs,重心是c,

第二次对b所在的子树进行点分治时,sz[b]的值是错的(此时应该把a看错是儿子,错误的将c看成是儿子)

思路来源说这样的复杂度好像是对的……但总感觉好像是能卡掉的……

所以每次确定重心前,先重新对v进行一下dfs确定正确的siz,再点分治下去,这样就没问题了

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=1e4+10;
const int K=1e7+10;
int head[N],cnt;
struct edge{int v,nex;ll w;}e[2*N];
void add(int u,int v,ll w){e[++cnt]=edge{v,head[u],w};head[u]=cnt;} 
bool vis[N];
int n,m,k,query[N],l,r,u,v;
int siz,f[N],sz[N],rt;
ll res,q[N],d[N],w;
void init(){
	cnt=0;
	memset(head,0,sizeof head);
}
//找下一次的重心rt 
void getrt(int u,int fa,bool op){
	f[u]=0;sz[u]=1;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		if(v==fa||vis[v])continue;
		getrt(v,u,op);
		f[u]=max(f[u],sz[v]);
		sz[u]+=sz[v];
	}
	if(op){
		f[u]=max(f[u],siz-sz[u]);
		if(f[u]<f[rt])rt=u;
	}
}
//计算重心u到子树内每个点的距离 
void getdis(int u,int fa){
	q[++r]=d[u];
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		ll w=e[i].w;
		if(v==fa||vis[v])continue;
		d[v]=d[u]+w;
		getdis(v,u);
	}
}
//计算以u为根的子树的答案
ll cal(int u,ll w){
	r=0;d[u]=w;
	getdis(u,0);
	l=1;ll ans=0;
	sort(q+1,q+r+1);
	while(l<r){
		if(k-q[l]<q[l])break;
		ll v=upper_bound(q+l+1,q+r+1,k-q[l])-lower_bound(q+l+1,q+r+1,k-q[l]);
		ans+=v;l++;
	}
	return ans;
}
void dfs(int u){
	//每次用在u的子树里任取减去在v的子树里的答案
	//每次只计算 必经过u的答案 
	res+=cal(u,0);
	vis[u]=1;
	for(int i=head[u];i;i=e[i].nex){
		int v=e[i].v;
		ll w=e[i].w;
		if(vis[v])continue;
		res-=cal(v,w);
		getrt(v,u,0);//获得正确的sz[v] 
		siz=sz[v];rt=0;
		getrt(v,u,1);
		dfs(rt);
	} 
 } 
int main(){
	scanf("%d%d",&n,&m);
	init();
	for(int i=1;i<n;++i){
		scanf("%d%d%lld",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	for(int i=1;i<=m;++i){
		scanf("%d",&query[i]);
 	} 
	for(int i=1;i<=m;++i){
		for(int j=1;j<=n;++j)
		vis[j]=0;
		res=0;k=query[i];
		f[0]=siz=n;rt=0;
		getrt(1,0,1),dfs(rt);
		puts(res?"AYE":"NAY");
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值