点分治超详细分析+视频讲解+题目大全(不再为点分治而烦恼)!

一.点分治原理分析及应用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二.点分治视频讲解

视频链接

https://www.bilibili.com/video/BV1PE41197md?p=1

三.点分治练习题

1.P3806 【模板】点分治1

1.P3806 【模板】点分治1
分析:两点的路径分为经过根节点和不经过根节点两种,那么我们可以点分治算法,对于每一个点都作为根处理一次,计算以该节点为根的子树的所有结点到根节点的距离,然后再用一个数组标记一下,我们就可以算出是否存在点对距离为k了
AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 1e4+10;
const int Maxk = 1e8+10; 
struct Edge{
	int v;
	int next;
	int w;
}edge[Maxn*2];
int head[Maxn];
int cnt;
void build(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].w = w;
    edge[cnt].next = head[u];
    head[u] = cnt;
    edge[++cnt].v = u;
    edge[cnt].w = w;
    edge[cnt].next = head[v];
    head[v] = cnt;
    return ;
}
//rt记录重心,sum记录当前树的大小,tot是计数器 
int n,m,rt,sum,tot;
int siz[Maxn];
int dep[Maxn];
int tmp[Maxn];//存储距离 
int maxp[Maxn];//maxp储存树的重心 
int Q[Maxn];//储存询问
//judge记录距离是否存在,ans询问的答案,vis标记点是否被删除 
bool judge[Maxk],ans[105],vis[Maxn]; 
int dis[Maxn];
//找重心,有点类似与树链剖分找重儿子 
void getrt(int u,int f){
	siz[u] = 1,maxp[u] = 0;//maxp初始化为0,maxp用于找重心 
	//遍历u的所有儿子,找出重儿子的大小maxp
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		//如果v是u的父节点或者v节点已经被删除,就continue  
		if(v==f||vis[v]) continue;
		getrt(v,u);
		siz[u]+=siz[v];
		//更新重儿子 
		if(siz[v]>maxp[u]){
			maxp[u] = siz[v];
		} 
	}
	//再用重儿子大小和u的父先节点比较 
	maxp[u] = max(maxp[u],sum-maxp[u]);
	//更新重心 
	if(maxp[u]<maxp[rt]){
	rt = u;//maxp[rt]也更新了 
	}
	return ; 
}
void getdis(int u,int f){
	/*储存u为根的树的所有子节点到u的路径长度*/
	tmp[++tot] = dis[u];//首先v-u存进来
    for(int i=head[u];i!=-1;i=edge[i].next){
    	int v = edge[i].v;
    	if(v==f||vis[v]) continue;
    	dis[v]=dis[u]+edge[i].w;
    	getdis(v,u);
	}
	return ; 
}
void solve(int u){
	static queue<int>q;
	for(int i=head[u];i!=-1;i=edge[i].next){
		tot = 0;
		int v = edge[i].v;
		if(vis[v]) continue;//已经被删除了的点 
		dis[v] = edge[i].w;
		getdis(v,u);
		for(int j=1;j<=tot;j++)//遍历所有距离 
		 for(int k=1;k<=m;k++){//遍历所有询问 
		 	if(Q[k]>=tmp[j]) ans[k]|=judge[Q[k]-tmp[j]];
		 }
		
		for(int j=1;j<=tot;j++){
			q.push(tmp[j]);
			judge[tmp[j]] = true;//设为true表示距离存在 
		}
	}
	/*把tmp数组距离初始化为false,防止影响下一个子树的判断*/
	while(!q.empty()){
		judge[q.front()] = false;
		q.pop();
	}
}
//分治 
void divide(int u){
	/*删除根(重心)节点,并且judge[0]=true
	因为点到它本身距离为0*/ 
	vis[u]=judge[0] = true;
	solve(u);//计算经过根节点的路径
	
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		/*如果该节点已经被删除(包括父节点也已经被删了,因为是从上到下处理的)则跳过*/
		if(vis[v]) continue;
		maxp[rt=0]=sum=siz[v];//此时要把以v节点为根的子树作为研究对象 
		getrt(v,0);//找v的重心 
		getrt(rt,0);//以重心为根重新跟新siz数组
		
		divide(rt);//对这颗子树进行点分治 
	} 
} 
void init(){
	memset(vis,false,sizeof(vis));
	memset(head,-1,sizeof(head));
	return ;
}
int main()
{
	init();
   cin>>n>>m;
   for(int i=1;i<n;i++){
   	int u,v,w;
   	cin>>u>>v>>w;
   	build(u,v,w);
   }
   for(int i=1;i<=m;i++) cin>>Q[i];
   //刚开始没有重心所以为rt=0,并且设置为最大值 
   maxp[0]=sum=n;
   
   getrt(1,0);//找重心
   /*!此时siz数组存放的是以1为根时子树大小,
    需要以找出以重心为根重算*/
    
   getrt(rt,0);
   
   divide(rt);//找好重心之后就可以开始分治了,求解答案
//cout<<"11\n";
   for(int i=1;i<=m;i++){
   	if(ans[i]) cout<<"AYE\n";
	else cout<<"NAY\n"; 
   } 
   return 0;
}

2.P2634 [国家集训队

2.P2634 [国家集训队]聪聪可可
分析:这道题就主要是算有多少点对(自己和自己也算一对)路径上的边权之和是3的倍数(注意0也是3的倍数),显然这道题先把边权转为点权,然后用点分治将每一个结点作为根处理一次以它为根的子树,对于每一个以该节点为根节点的子树,算出所有结点到根节点的距离,显然要计算3的倍数的路径有多少,如果数组存路径长度的话肯定会MLE,所以不妨把距离%3,然后算有多少点对的距离%3为0即可,这样我们就只需要给数组开3个空间大大节省了空间
AC代码:

/*点分治*/
#include<bits/stdc++.h>
using namespace std;
const int Maxn = 2e4+10;
struct E{
	int v;
	int w;
	int next;
}edge[Maxn*2];
int head[Maxn];
int n;
int maxp[Maxn];
int siz[Maxn];
bool vis[Maxn];
int dis[Maxn];
int tmp[Maxn];
int ans = 0;
int all = 0;
int cnt = 0;
int tot = 0;
int judge[3]={0};//记录距离的个数,这里是距离%3后 
int sum = 0;
int rt;
/*链式前向星*/
void build(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].w = w ;
	edge[cnt].next = head[u];
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].w = w;
	edge[cnt].next = head[v];
	head[v] = cnt;
	return ;
}
/*记录距离*/
void getdis(int u,int f){
	tmp[++tot] = dis[u];
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		dis[v] = dis[u]+edge[i].w;
		getdis(v,u);
	}
	return ;
} 
/*找树的重心,算size*/
void getrt(int u,int f){
    maxp[u] = 0;
    siz[u] = 1;
    for(int i=head[u];i!=-1;i=edge[i].next){
    	int v = edge[i].v;
    	//如果v是u的父节点或者v以及被删除了 
    	if(v==f||vis[v]) continue;
    	getrt(v,u);//更新siz[v] 
		siz[u]+=siz[v];
		if(siz[v]>maxp[u]){
			maxp[u] = siz[v];
		}    
	}
	maxp[u] = max(maxp[u],sum-maxp[u]);
	if(maxp[u]<=maxp[rt]){
		rt = u;
	}
	return ;
}
void solve(int u){
	for(int i=head[u];i!=-1;i=edge[i].next){
		tot = 0;
		int v = edge[i].v;
		if(vis[v]) continue;//以及被删除了 
		dis[v] = edge[i].w;
		getdis(v,u);
		for(int i=1;i<=tot;i++){
		    int len = tmp[i]%3;
			if(len==0) ans++;    
		    int need = (3-len)%3;//需要的 
		    ans+=judge[need];
		   
		}
		/*不能放到上面的for循环处理,因为一条链上的不能重算*/
		for(int i=1;i<=tot;i++){
			int len = tmp[i]%3;
			judge[len]++;
		}		 
	}
	for(int i=0;i<3;i++) judge[i] = 0;
	return ;
}
/*点分治处理节点答案*/
void divide(int u,int f){
	vis[u] = true;//vis[u]=1表示被删除
	solve(u);//算经过u节点的路径 
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(vis[v]) continue;
		maxp[rt=0]=sum=siz[v];
		getrt(v,0);//以v为根节点的子树为对象,找它的重心 
		getrt(rt,0);//以重心为根节点重新计算siz
		divide(rt,0); 
		cout<<rt<<"rt\n";
	}  
	return ;
}
void init(){
	memset(vis,false,sizeof(vis));
	memset(head,-1,sizeof(head));
	return ;
}
int main(){
	init();
	scanf("%d",&n);
	all = n*n;
	for(int i=1;i<n;i++){
		int u,v,w;
		scanf("%d %d %d",&u,&v,&w);
		build(u,v,w);
	}
	maxp[rt=0]=sum=n;
	getrt(1,0);
	getrt(rt,0);
	divide(rt,0);
	ans*=2;//别忘了ans*2,因为(3,5)和(5,3)算两个答案
	ans+=n;
	int gcd = __gcd(ans,all);
	ans/=gcd;
	all/=gcd;
	printf("%d/%d",ans,all);
	return 0;	
	
}

3.P4149 [IOI2011]Race

3.P4149 [IOI2011]Race
分析:这道题是2011IOI的一道题目,但是并不难,只是需要有一点巧妙的思想,这道题就是算简单路径边权和为k,且边数最小,输出次边数,显然第一步,先把边权转为点权(套路操作),然后同样是点分治的处理方法,然后用点分治将每一个结点作为根处理一次以它为根的子树,对于每一个以该节点为根节点的子树,算出所有结点到根节点的距离,算后用数组记录下来,然而这里的最大n是2e5,最大边权为1e6,所以一条路径的所有边权可能超出int,用数组完全存不下,所以我们采用了一点剪枝的思想,我们可以看到最大的k只有1e6,所以我们对于一条路径边权之和>k的对答案没有贡献的我们就直接舍弃,这样就不用存它了,那么我们储存距离数组的大小就只需要开到1e6,这样就可以AC了

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 2e5+10;
const int N = 1e7+10;
int judge[N];
int n,k;
struct e{
   int v;
   int next;
   int w;
}edge[Maxn*2];
int head[Maxn];
int cnt;
int siz[Maxn];
int maxp[Maxn];
int tmp[Maxn];
int dis[Maxn];
int ans;
int depp[Maxn];
bool vis[Maxn];
int sum,rt,tot;
void build(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	edge[cnt].w = w;
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	head[v] = cnt;
	edge[cnt].w = w;
	return ;
}
/*找重心,计算siz*/
void getrt(int u,int f){
	siz[u] = 1;
	maxp[u] = 0;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(v==f||vis[v]) continue;
		getrt(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxp[u]) maxp[u] = siz[v];
	}
	maxp[u] = max(maxp[u],sum-maxp[u]);
	if(maxp[u]<maxp[rt]) rt=u;
	return ;
}
/*处理距离*/
void getdis(int u,int f,int dep){
	tmp[++tot] = dis[u];
	//cout<<u<<' '<<dis[u]<<"gd\n";
	depp[tot] = dep;
 	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		dis[v] = dis[u]+edge[i].w;
		getdis(v,u,dep+1);
	}
	return ;
}
/*solve处理节点*/
void solve(int u){
	static queue<int>q;
//	cout<<u<<"u\n";
	for(int i=head[u];i!=-1;i=edge[i].next){
		tot = 0;
		int v = edge[i].v;
		if(vis[v]) continue;
		dis[v]=edge[i].w;
		getdis(v,u,1);
		for(int i=1;i<=tot;i++){
			if(k>=tmp[i])
			q.push(tmp[i]);
			if(k>=tmp[i])
			ans = min(ans,depp[i]+judge[k-tmp[i]]);
		}
		/*更新路径*/
		for(int i=1;i<=tot;i++){
			if(tmp[i]>k) continue;
			judge[tmp[i]] = min(judge[tmp[i]],depp[i]);
		}
	}
	while(!q.empty()){
		judge[q.front()] = 0x3f3f3f3f;
		q.pop();
	}
	return ;
}
/*点分治*/
void divide(int u,int f){
	vis[u] = true;
	solve(u);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(vis[v]||v==f) continue;
		maxp[rt=0] = sum = siz[v];
		getrt(v,0);
		getrt(rt,0);
		divide(rt,0);
	//	cout<<rt<<"rt\n";
	}
	return ;
}
void init(){
	memset(head,-1,sizeof(head));
	memset(vis,false,sizeof(vis));
	memset(judge,0x3f3f3f3f,sizeof(judge));
	ans = 0x3f3f3f3f;
	judge[0] = 0;
	return ;
}
int main(){
	init(); 
	scanf("%d %d",&n,&k);
	for(int i=1;i<n;i++){
		int u,v,w;
		scanf("%d %d %d",&u,&v,&w);
		u++;v++;
		build(u,v,w);
	}
	maxp[rt=0] = sum = n;
	getrt(1,0);
	getrt(rt,0);
	//cout<<rt<<'\n';
	divide(rt,0);
	if(ans==0x3f3f3f3f) ans=-1;
	cout<<ans<<'\n';
	return 0;
}

4.CF161D Distance in Tree

4.CF161D Distance in Tree
分析:采用点分治思想,套模板即可,没啥思维难度,这道题没给边权,默认边权为1,这到题洛谷叫不需要看longlong,因为数据量小,但是如果在CodeForces上面就需要开longlong
AC代码:

/*点分治*/
#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 5e4+10;
struct e{
	int w;
	int v;
	int next;
}edge[Maxn<<1];
int head[Maxn];
int n,k;
int cnt=0;
int siz[Maxn];
bool vis[Maxn];
int judge[Maxn];
int tmp[Maxn];
int tot=0;
int ans = 0;
int sum=0;
int rt;
int dis[Maxn]; 
int maxp[Maxn];
void build(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	edge[cnt].w = w;
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	edge[cnt].w = w;
	head[v] = cnt;
	return ;	
}
void getrt(int u,int f){
	siz[u] = 1;
	maxp[u] = 0;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
	    if(v==f||vis[v]) continue;
	    getrt(v,u);
	    siz[u]+=siz[v];
	    if(siz[v]>maxp[u]) maxp[u] = siz[v];
	}
	maxp[u] = max(maxp[u],sum-maxp[u]);
	if(maxp[u]<=maxp[rt]) rt = u;
	return ;
}
void getdis(int u,int f){
	tmp[++tot] = dis[u];
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		dis[v] = dis[u]+edge[i].w;
		getdis(v,u);
	}
	return ;
}
void solve(int u){
	static queue<int>q;
	for(int i=head[u];i!=-1;i=edge[i].next){
		tot = 0;
		int v = edge[i].v;
		if(vis[v]) continue;
		dis[v] = edge[i].w;
		getdis(v,u);
		for(int i=1;i<=tot;i++){
			if(tmp[i]==k) ans++;
			else if(tmp[i]<k){
				ans+=judge[k-tmp[i]];
			}
		}
		for(int i=1;i<=tot;i++){
			if(tmp[i]<k){
				q.push(tmp[i]);
				judge[tmp[i]]++;
			}
		}
	}
	while(!q.empty()){
		judge[q.front()]--;
		q.pop();
	}
	return ;
}
void divide(int u,int f){
	vis[u] = true;
	solve(u);
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		maxp[rt=0] = sum = siz[v];
		getrt(v,0);
		getrt(rt,0);
		divide(rt,0);
	}
	return ;
}
void init(){
	memset(head,-1,sizeof(head));
	memset(vis,false,sizeof(vis));
	return ;
}
int main(){
	init();
	scanf("%d %d",&n,&k);
	for(int i=1;i<n;i++){
		int v,u;
		scanf("%d %d",&v,&u);
		build(v,u,1);
	}
	maxp[rt=0] = sum =n; 
    getrt(1,0);
    getrt(rt,0);
    divide(rt,0);
    cout<<ans<<'\n';
    return 0;
}

5.P4178 Tree

P4178 Tree
分析:这道题和第四题有点大同小异,这道题是算出两点之间距离小于等于k的点对数,所以我们还是采用点分治的思想处理每一个点,只不过我们需要用线段树来维护到根节点距离为i的点个数,然后用区间查询来快速查询到根节点距离为[1,i]的个数(注意这到题仍然,对于路径长度大于k的直接剪掉,不然数组存不下).

AC代码:

#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 4e4+10;
struct e{
	int w;
	int v;
	int next;
}edge[Maxn<<1];
int tr[Maxn*4]={0};//线段树维护区间和  
int cnt;
bool vis[Maxn];
int head[Maxn];
int maxp[Maxn];
int dis[Maxn]; 
int ans;
int siz[Maxn];
int judge[Maxn];
int num=0;
int tmp[Maxn];
int rt,sum,tot;
int n,k;
void pushup(int dep){
	tr[dep] = tr[ls]+tr[rs];
	return ;
}
void update(int l,int r,int ql,int qr,int dep,int val){
	if(ql<=l&&r<=qr){
	    tr[dep] += (r-l+1)*val;
	    return ;
	}
	int mid = l+r>>1;
	if(ql<=mid) update(l,mid,ql,qr,ls,val);
	if(qr>mid) update(mid+1,r,ql,qr,rs,val);
	pushup(dep);
	return ;
}
int query(int l,int r,int ql,int qr,int dep){
	if(ql<=l&&r<=qr){
		return tr[dep];
	}
	int mid = l+r>>1;
	int ans = 0;
	if(ql<=mid) ans+=query(l,mid,ql,qr,ls); 
	if(qr>mid) ans+=query(mid+1,r,ql,qr,rs);
	return ans; 
}
void build(int u,int v,int w){
	edge[++cnt].v = v;
	edge[cnt].next = head[u];
	edge[cnt].w = w;
	head[u] = cnt;
	edge[++cnt].v = u;
	edge[cnt].next = head[v];
	edge[cnt].w = w;
	head[v] = cnt;
	return ;
}
/*找重心,计算siz*/
void getrt(int u,int f){
	maxp[u] = 0;
	siz[u] = 1;
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		getrt(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxp[u]) maxp[u] = siz[v];
	}
	maxp[u] = max(maxp[u],sum-maxp[u]);
	if(maxp[u]<=maxp[rt]) rt=u;
	return ;
}
/*处理dis*/
void getdis(int u,int f){
	tmp[++tot] = dis[u];
	for(int i=head[u];i!=-1;i=edge[i].next){
		int v=edge[i].v;
		if(v==f||vis[v]) continue;
		dis[v] = dis[u]+edge[i].w;
		getdis(v,u);
	}
	return ;
}
/*solve处理节点*/
void solve(int u){
	
	for(int i=head[u];i!=-1;i=edge[i].next){
		tot = 0;
		int v = edge[i].v;
		if(vis[v]) continue;
		dis[v] = edge[i].w;
		getdis(v,u);
		for(int i=1;i<=tot;i++){
		    if(tmp[i]==k){
		    	ans++;
			}
			else if(tmp[i]<k){
				ans++;
				/*算到u结点距离为[1,k-tmp[i]]的点有多少个*/
				ans+=query(1,k,1,k-tmp[i],1);
			}
		}
		for(int i=1;i<=tot;i++){
		    /*>k对答案没有贡献的不要*/
			if(tmp[i]<=k){
			judge[++num] = tmp[i];
		    update(1,k,tmp[i],tmp[i],1,1);
			}
		}
	}
	for(int i=1;i<=num;i++){
		update(1,k,judge[i],judge[i],1,-1);
	}
	return ;
}
/*点分治*/
void divide(int u,int f){
	vis[u] = true;
	solve(u);
	for(int i=head[u];i!=-1;i=edge[i].next){
		num = 0;
		int v = edge[i].v;
		if(v==f||vis[v]) continue;
		maxp[rt=0] = sum = siz[v];
		getrt(v,0);
		getrt(rt,0);
		divide(rt,0);
	}
	return ;
}
void init()
{
	memset(head,-1,sizeof(head));
	memset(vis,false,sizeof(vis));
	//memset(judge,0,sizeof(vis))
	return ;
}
int main(){
	init();
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int u,v,w;
		scanf("%d %d %d",&u,&v,&w);
		build(v,u,w);
	}
	scanf("%d",&k);
	maxp[rt=0]=sum=n;
	getrt(1,0);
	getrt(rt,0);
	divide(rt,0);
	cout<<ans<<'\n';
}

声明:本博客采用了某视频博主的视频资源,如有侵权请立刻告知,还望谅解谢谢!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值