点分治学习笔记

前置知识:树的重心

对于一颗无根树上的每一个点,计算其所有子树中最大的子节点数,这个值最小的点就是树的重心,用树形dp可以\(O(n)\)求解

1. 定义

点分治,又叫树分治,点分治是一种在树上进行路径静态统计的算法,所谓树上的静态统计,并不是像树剖一样维护路径最值,路径之和一类的统计,点分治的本质其实是将一棵树拆分成许多棵子树去处理,并不断进行,通常的,对于点分治能解决的问题,都是与树上路径统计有关的问题

2. 具体思路

2.1.详解

对于一个序列上的区间和等操作,我们可以将原问题分解为几个子问题来求解之后再一一合并答案,而在树上我们依然可以进行这种操作

先看一道例题 P3806【模板】点分治 1

给定一棵有 n 个点的带边权树,m 次询问,每次询问给出 k,询问树上距离为 k 的点对是否存在。

我们先随意选一个节点rt作为根,则完全位于其子树的节点有两种,一种是经过根的路径,另一种不经过,而经过的有可分为两种,一种是以该点为端点,另一种则是两个端点皆不为根的情况,第二种有可以由前者的链合并得到

所以,对于所枚举的根rt,我们先计算在其子树中经过该点的路径对答案的贡献,再递归其子树内不经过该点的路径进行求解

在本题中,对于经过rt的路径,我们先枚举其子节点ch,以ch为根计算ch中所有节点到rt的距离,记节点i的当前节点rt的距离为\(dis_i\)\(t_x\)表示在之前处理过的子树中是否存在一个节点v使\(dis_v=x\),若一个询问k满足\(t_{k-dis_i}=1\),则存在一条长为k的路径,在计算完ch子树的边能否成为答案之后,将其存入t中即可

但是树上每一个子树的节点数量是不确定的,不能单单取中点,或直接取1号子树

例如,若每一条边(u,v)满足u=v-1,则直接取1的时间复杂度为\(O(n)\),但若取\(\frac{n}{2}\),则时间复杂度为\(O(logn)\)

所以考虑树的重心,当选择树的重心为分支点时,是最优的

2.2. 正确性

关键在于未作为rt的节点是否考虑完全,可以发现,在统计的过程中,因为统计的是链,且是由深度大的点的权值在根处合并,所以最终的路径也会经过这个点,且以它为端点和不以它为端点的路径都存在,所以情况必然是完整的

2.3. 代码

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,head[10004],edgenum,b[105];
struct edge{
	int to,nxt,val;
}e[20004];
void add_edge(int u,int v,int w)
{
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	e[edgenum].val=w;
	head[u]=edgenum;
}
int size[10004],mx[10004],rt,sum,dis[10004];
bool res[105],vis[10004];
void dfs1(int u,int fa)
{
	size[u]=1;
	mx[u]=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dfs1(v,u);
		mx[u]=max(mx[u],size[v]);
		size[u]+=size[v];
	}
	mx[u]=max(mx[u],sum-size[u]);
	if(mx[u]<mx[rt]) rt=u;
}
bool t[10000007];
queue<int>q;
int d[10004],cnt;
void dfs2(int u,int fa)
{
	d[++cnt]=dis[u];
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
		dis[v]=dis[u]+e[i].val;
		dfs2(v,u);
	}
}
void dfs(int u,int fa)
{
//	printf("%d %d\n",u,fa);
	t[0]=vis[u]=1;
	q.push(0);
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
      	dis[v]=e[i].val;
      	dfs2(v,u);
      	for(int j=1;j<=cnt;j++)
      	{
       		for(int k=1;k<=m;k++)
        	{
       			if(b[k]>=d[j]) res[k]|=t[b[k]-d[j]];
			}
		}
     	for(int j=1;j<=cnt;j++)
     	{
       		if(d[j]<=1e7)
			{
				q.push(d[j]);
				t[d[j]]=1;
			}
		}
    	cnt=0;
	}
	while(!q.empty())
	{
		t[q.front()]=0;
		q.pop();
	}
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(v==fa||vis[v]) continue;
		sum=size[v];
		rt=0;
		mx[rt]=1e9;
		dfs1(v,u);
		dfs1(rt,-1);
		dfs(rt,u);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&b[i]);
	}
	mx[0]=1e9,sum=n;
	dfs1(1,-1);
	dfs1(rt,-1);
	dfs(rt,-1);
	for(int i=1;i<=m;i++)
	{
		if(res[i]) printf("AYE\n");
		else printf("NAY\n");
	}
	return 0;
}
posted @ 2024-04-11 21:37  wangsiqi2010916  阅读(10)  评论(0编辑  收藏  举报