点分治相关 学习笔记

点分治相关

前置知识:树的重心

1.定义:重心是指树中的一个节点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,这个节点被称为树的重心。

(注:这里的 子树 指的是无根树的子树,即包括向上的那棵子树,不包括整棵树自身* 。)

2.性质:

  • 树的重心如果不唯一,则至多两个,且两个重心相邻。

  • 以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。

  • 树上所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,到它们的距离和一样。

  • 把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树重心的路径上。

3.求法

记录 \(size_u\)\(u\) 的最大子树节点数,\(sum_u\) 记录以 \(u\) 为根的子树节点数,所以\(n-sum\)\(u\) 上面的部分的节点数,有 \(ans=\max(size,n-sum)\)

然后在树上跑 dp 更新答案即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n;
struct edge{
	int to,nxt;
}e[2*maxn];
int head[maxn],edgenum;
void add_edge(int u,int v){
	e[++edgenum].nxt=head[u];
	e[edgenum].to=v;
	head[u]=edgenum;
}
int siz[maxn],ans=1e9,pos;//pos为重心位置 
void dfs(int u,int fa){
	siz[u]=1;
	int mx=0;//存储将该点删去后 剩下各联通块大小的最大值 
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,u);
		siz[u]+=siz[v];
		mx=max(mx,siz[v]);
	} 
	mx=max(mx,n-siz[u]);
	if(mx<ans) ans=mx,pos=x;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	cout<<ans<<endl;
}

点分治

1.定义

点分治,是一种在树上进行路径静态统计的算法,与树剖不同,并不是维护路径最值,路径和之类的统计。点分治本质上是将一棵树拆分成许多棵子树处理,并不断进行。对于点分治能解决的问题,都与树上路径的统治和询问有关。

树上的路径可以分为两类,一类是经过根节点的路径,一类是不经过根节点的路径。

对于不经过根的路径,它们一定在根的某个子节点所构成的子树中,然后我们可以通过递归找到这个路径所经过的根节点。

这样就对答案进行了划分。树T的答案=T中经过根的路径的答案+根的所有子节点构成子树的答案。对于第二部分的答案,可以递归处理。我们只需要考虑如何计算第一部分的答案,即计算经过根的路径的个数。

2.具体做法

对于经过根节点的路径,可以预处理出每个点到根节点的路径,然后 \(dis[u,v]=dis[u,root]+dis[v,root]\) 。这时要注意排除不合法路径(即 \(u,v\) 在同一棵子树内,两者之间的路径不经过根节点),先把前面子树中各点到根的距离存入一个队列 \(q_i\) ,开一个 bool 数组存队列中的距离 \(judge_i\) (其实也就相当于一个桶)。再枚举当前子树中各点到根的距离 \(dis_j\) ,若询问 \(k\)\(dis_j\) 的差存在,说明此解合法。

对于不经过根节点的路径,可以对子树不断分治,转化为经过根节点的路径。

3.例题

点分治板子 P3806

代码
/*点分治
1.找出树的重心做根
2.求出子树中各点到根节点的距离
3.对当前树统计答案
4.分治各个子树,重复以上操作*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
const int inf=10000005;
struct edge{
	int to,nxt,val;
}e[2*maxn];
int head[maxn],edgenum;
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 del[maxn],siz[maxn],mxs,sum,root;//del表示此点是否作为重心分治过删掉过
int dis[maxn],d[maxn],cnt;//dis表示到根节点的距离
int ans[maxn],q[inf],judge[inf];
int n,m,ask[maxn]; 

void getroot(int u,int fa){//找当前 u 子树的重心root 作为根节点 
	siz[u]=1;
	int s=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || del[v]) continue;
		getroot(v,u);
		siz[u]+=siz[v];
		s=max(s,siz[v]);
	}
	s=max(s,sum-siz[u]);
	if(s<mxs) mxs=s,root=u;
}
void getdis(int u,int fa){//求子树所有点到重心的距离 
	dis[++cnt]=d[u];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || del[v]) continue;
		d[v]=d[u]+e[i].val;
		getdis(v,u);
	}
}

void calc(int u){
	del[u]=judge[0]=1;//judge[0]=1,说明距离为0的点是存在的,就是根节点到自己的距离 
	int p=0;//计算经过根u的路径
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(del[v]) continue;
		cnt=0;//用于记录v这棵子树的节点到根节点的路径条数
		d[v]=e[i].val;//v到u的初值 是 uv 这条边的权值 
		getdis(v,u);//求出v这个子树每个点到u的距离(获取子树节点到根节点的距离
		for(int j=1;j<=cnt;j++){
			for(int k=1;k<=m;k++){
				if(ask[k]>=dis[j]){//dis[j]记录的是 本次遍历的子树节点到根节点距离为dis的距离是否存在 
					ans[k]|=judge[ask[k]-dis[j]];//judge 是其他子树 
				}
			}
		}
		for(int j=1;j<=cnt;j++){
			if(dis[j]<inf){
				q[++p]=dis[j],judge[q[p]]=1;
			}
		}
		//记录下合法距离
		//将本棵子树记录下的路径距离存进judge数组,对于下一棵子树而言,本次的距离就是可以组合的  
	} 
	for(int i=1;i<=p;i++) judge[q[i]]=0;
}
void divide(int u){
	//计算经过根u的路径 
	calc(u);
	del[u]=1;
	//对u的子树进行分治 
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(del[v]) continue;
		mxs=sum=siz[v];//对哪个子树求根,就用哪个子树大小初始化mxs和sum 
		getroot(v,0);//求子树v的根(重心 
		divide(root);//分治 
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		add_edge(u,v,w),add_edge(v,u,w);
	}
	for(int i=1;i<=m;i++){
		cin>>ask[i];
	}
	mxs=sum=n;
	getroot(1,0);//任意找一个点开始,找重心 
	getroot(root,0);//重构siz 求以 root为根的每个子树的节点个数 
	divide(root);
	for(int i=1;i<=m;i++){
		if(ans[i]) cout<<"AYE"<<endl;
		else cout<<"NAY"<<endl;
	}
}

[IOI2011]Race

点击查看代码
/*对距离为i的点建立 tmp[i] 表示在当前递归到的子树中,走到距离为i的顶点最少需要走多少边
点分治,对每棵子树遍历,求出每个点i到root的距离 dis[i],走过的边数d[i]
然后就有 ans=min(ans,tmp[k-dis[i]]+d[i])

这里特别注意一下初始化和清空
遍历完这棵子树再修改被访问了的 tmp[dis[i]] 然后下一棵
所有子树遍历完了之后 再遍历一遍所有节点 把修改到的tmp值变回inf
*/
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int inf=1e9;
int n,k,ans=inf;
struct edge{
	int to,nxt,val;
}e[2*maxn];
int head[maxn],edgenum;
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 del[maxn];
int siz[maxn],mxs,sum,root;
void getroot(int u,int fa){
	siz[u]=1; 
	int s=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || del[v]) continue;
		getroot(v,u);
		siz[u]+=siz[v];
		s=max(s,siz[v]);
	}
	s=max(s,sum-siz[u]);
	if(s<mxs) mxs=s,root=u;
}
int dis[maxn],d[maxn],tmp[10*maxn];
//d[i]表示当前树走到i点走过的边数 dis是距离 
void getdis(int u,int fa){
	if(dis[u]<=k){
		ans=min(ans,tmp[k-dis[u]]+d[u]);
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || del[v]) continue;
		dis[v]=dis[u]+e[i].val;
		d[v]=d[u]+1;
		getdis(v,u);
	}
}
/*getdis(v,0) 后每个子树会得到每个点到根节点的dis 然后尝试更新这个距离的tmp
fl表示求完当前子树的信息来更新tmp / 准备处理下一棵子树需要把tmp初始化为inf
*/
void update(int u,int fa,int fl){
	if(dis[u]<=k){
		if(fl) tmp[dis[u]]=min(tmp[dis[u]],d[u]);//fl==1 代表更新 
		else tmp[dis[u]]=inf;//否则是初始化 
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==fa || del[v]) continue;
		update(v,u,fl);
	}
}
void solve(int u){
	del[u]=1;
	tmp[0]=0;
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(del[v]) continue;
		dis[v]=e[i].val;
		d[v]=1;
		getdis(v,0);
//以v为根 求其他节点到v的距离 
		update(v,0,1);
//用v子树上的点尝试更新各个长度的tmp 
	}
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(!del[v]) update(v,0,0);
//把没被当作根分治的节点v的 所有子树中节点的tmp被初始化为inf 
	}
	//分治 
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(del[v]) continue;
		sum=mxs=siz[v];
		getroot(v,0);
		solve(root);
	}
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>k;
	for(int i=1;i<=k;i++){
		tmp[i]=inf;
	} 
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		u++,v++;
		add_edge(u,v,w),add_edge(v,u,w);
	}
	sum=mxs=n;
	getroot(1,0);
	solve(root);
	if(ans!=inf) cout<<ans<<endl;
	else cout<<-1<<endl;
}

靠近

posted @ 2025-04-06 10:31  Aapwp  阅读(22)  评论(0)    收藏  举报
我给你一个没有信仰的人的忠诚