点分治

点分治

1.给定一个带边权的树,共有 \(m\) 个询问,询问距离为 \(k\) 的点对是否存在

做法1:暴力dfs

做法2:\(lca\) (时间复杂度\(O(n^2\log n)\)

做法3:点分治 (时间复杂度\(O(n\log n)\))

思路:

1.取一个节点 \(u\)

2.统计经过\(u\)的链

经过\(u\) 的链两端点必定在不同子树中

记录子节点到根节点的距离\(dist[i]\),依次统计所有子树,开一个\(lens\)数组记录已有路径长度。

当遍历一个子树时,查找是否存在\(lens[k-dist[i]]\)即可。

遍历完一个子树,开个栈存储该子树的\(dist\),再把该子树的路径全部加到\(lens\)中。

清空\(lens\)数组,为保证时间复杂度,开个栈记录添加的\(lens\),只清空栈中的元素即可

3.向下递归

如何选取节点:

若该树是一条链,如果每次都找其儿子递归,要递归\(n\)次,复杂度为\(O(n^2)\)

所以每次选取的节点\(u\)应该为该树的重心,只需递归\(\log n\)次,复杂度为\(O(n\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=10010,M=20010,K=100000100,INF=0x3f3f3f3f;
int n,q,rt,minn=INF,root,maxx2;
int qs[N],ans[N];
int h[N],e[M],ne[M],w[M],idx;
int s[N],lens[K];
int stk[N],tt,stk2[N],tt2;
bool del[N];//该点是否已被遍历
inline int read()
{
	int x=0;
	char c=getchar();
	while(c<'0'||c>'9')c=getchar();
	while(c>='0'&&c<='9'){
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	return x;
}
void add(int a,int b,int c)
{
	e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int fa,int u,int zs)//找重心
{
	int maxx=0;
	s[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa||del[j])continue;
		dfs(u,j,zs);
		s[u]+=s[j];
		maxx=max(maxx,s[j]);
	}
	maxx=max(maxx,zs-s[u]);
	if(maxx<minn)minn=maxx,rt=u;
}
void dfs2(int fa,int u,int len)//统计以u为根的子树穿过u的路径
{
	if(len>maxx2)return;//长度大于询问的最大值,不用再向下统计
	stk[++tt]=len;
	for(int i=0;i<q;i++){
		if(qs[i]-len>=0&&qs[i]-len<K){
			if(lens[qs[i]-len]||len==qs[i])ans[i]=1;//存在另一子树长度为qs[i]-len,或其本身长度为qa[i]
		}
	}
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j]||j==fa)continue;
		dfs2(u,j,len+w[i]);	
	}
}
void query(int u)
{
	del[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j])continue;
		dfs2(u,j,w[i]);
		for(int l=1;l<=tt;l++){
			if(!lens[stk[l]])stk2[++tt2]=stk[l];
			lens[stk[l]]++;//将该子树的len加到lens中
		}
		tt=0;
	}
	for(int i=1;i<=tt2;i++)lens[stk2[i]]=0;//只清空栈中元素
	tt2=0;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(del[j])continue;
		minn=INF,rt=-1;
		int zs=s[j];
		dfs(u,j,zs);
		dfs(0,rt,zs);//维护size值(似乎不用)
		query(rt);//从每个子树重心开始递归
	}
}
int main()
{
	memset(h,-1,sizeof h);
	n=read(),q=read();
	for(int i=1;i<n;i++){
		int a,b,c;
		a=read(),b=read(),c=read();
		add(a,b,c),add(b,a,c);
	}
	dfs(0,1,n);
	root=rt;
	for(int i=0;i<q;i++)qs[i]=read(),maxx2=max(maxx2,qs[i]);//预处理出询问,统一查找
	query(root);
	for(int i=0;i<q;i++){
		if(ans[i])puts("AYE");
		else puts("NAY");
	}
	return 0;
} 

P4178 Tree

给定一棵 n 个节点的树,每条边有边权,求出树上两点距离小于等于 k的点对数量。

可用线段树维护,也有一种容斥+双指针的方法。

处理当前子树经过其根节点的所有长度,存到a数组中:

void getdis(int fa,int u,int len)
{
	dis[u]=len;
	a[++cnt]=dis[u];
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa||del[j])continue;
		getdis(u,j,len+w[i]);
	}
}
posted @ 2023-10-12 21:52  lza0v0  阅读(38)  评论(0)    收藏  举报