ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

点分治与点分树 学习笔记

2021-01-05 23:34:09  阅读:241  来源: 互联网

标签:rt 分树 重心 int siz 分治 笔记 vis first


一、引入

我们看这样的一道题:给定一棵有\(m\)的点的树,询问树上是否存在距离为\(k\)的点对,有\(m\)组询问,\(nm\le 10^6\).

容易想到的做法是暴力,即对于每一个点对都暴力判断,显然,它的时间复杂度不够优秀。

点分治,就是解决像这道题一样需要大规模处理树上路径的问题的一种好方法。

二、算法流程

考虑这样的情况,我们面对的并不是一棵树,而是一个序列的是有关区间的区间和之类的问题。

对于这样的问题,我们知道可以通过选中点,分治处理左右部分,然后单独考虑跨过中点的区间的贡献,而处理后者往往比处理原问题容易地多。

我们希望将这样的分治策略扩展的树上,但我们该如何定义树上的中点呢?

这里我们引入一个概念——树的重心

它的定义是:在一棵树上,找到一个点,使以这个点作为根时,所有子树中大小最大的一个的大小最小(一棵子树的大小就是这棵子树的节点个数),那么这个点就是重心。

根据定义,一旦我们选择重心,然后递归处理每一棵子树,每一棵子树的大小都不会超过原树的\(\frac 12\),这保证了点分治的复杂度。

于是,我们就能写出算法流程了:

  • 1.找到重心
  • 2.处理经过重心的路径的贡献
  • 3.递归处理重心的所有子树

三、实现

寻找重心部分:

int rt,mxsiz;
inline void getsiz(int u,int f){
	siz[u]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;
		getsiz(v,u);siz[u]+=siz[v];
	}
	return ;
}
int dis[N],d[N];
inline void findrt(int u,int f,int sum){//sum是目前正在处理的这棵子树的大小
	int mx=sum-siz[u];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;//vis是对已经选择过的重心打的标记,这样就能防止遍历到其他子树上	
		findrt(v,u,sum);
		mx=max(mx,siz[v]);
	}
	if(mx<mxsiz) mxsiz=mx,rt=u;
}

接下来是分治的实现方式:

首先先找到重心,然后标记重心以防遍历子树时通过重心跑到另一个子树上

然后处理经过重心的路径,也就是这里的\(calc(rt)\),\(calc\)是每道题目都不相同的函数。

一般实现方式是:依次遍历子树,增加这棵子树中的点和已经遍历过的点的贡献

inline void solve(int u){
	rt=0;mxsiz=0x3f3f3f3f;sz=0;
	getsiz(u,0);
	findrt(u,0,siz[u]);
	vis[rt]=1;
	ans+=calc(rt);
	for(int i=first[rt];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		solve(v);
	}
}

接下来我们通过例题来讲解\(calc\)函数的\(2\)种实现方式:

四、例题

洛谷模板题

这就是我们在引入中所提到的题目。

我们可以开一个数组记录已经遍历过的节点到根的距离,增加一棵子树时,就依次考虑这棵树中所有节点到根的距离\(d\),并判断一下是否已经有距离为\(k-d\)的点即可:

#include<bits/stdc++.h>
using namespace std;
const int M=1e7+10;
const int N=2e5+10;
int n,m,k,rt,sz,mxsiz,mxk,ans=0,vis[N],siz[N],cnt,first[N],w[M],K[N],anss[N];
struct node{
	int v,w,nxt;
}e[N<<1];
inline void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
inline void getsiz(int u,int f){
	siz[u]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;
		getsiz(v,u);siz[u]+=siz[v];
	}
	return ;
}
int dis[N],d[N];
inline void findrt(int u,int f,int sum){
	int mx=sum-siz[u];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;	
		findrt(v,u,sum);
		mx=max(mx,siz[v]);
	}
	if(mx<mxsiz) mxsiz=mx,rt=u;
}
inline void getdis(int u,int f){
	dis[++sz]=d[u];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;
		d[v]=d[u]+e[i].w;
		getdis(v,u);
	}
}
inline void calc(int u){
	vector<int> used; 
	w[0]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		sz=0;d[v]=e[i].w;
		getdis(v,u);
		for(int t=1;t<=m;++t){
			if(anss[t]) continue;
			for(int j=1;j<=sz;++j){
				if(dis[j]>K[t]) continue;
				if(w[K[t]-dis[j]]){
					anss[t]=1;
					break;
				}
			}
		}
		for(int j=sz;j>0;j--)
			if(dis[j]<=mxk) used.push_back(dis[j]),w[dis[j]]=1;
	}
	for(int i=0;i<used.size();++i) w[used[i]]=0;
}
inline void solve(int u){
	rt=0;mxsiz=0x3f3f3f3f;sz=0;
	getsiz(u,0);
	findrt(u,0,siz[u]);
	vis[rt]=1;
	calc(rt);
	for(int i=first[rt];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		solve(v);
	}
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1,u,v,w;i<n;++i){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	for(int i=1;i<=m;++i) scanf("%d",&K[i]),mxk=max(mxk,K[i]);
	solve(1);
	for(int i=1;i<=m;++i)
		if(anss[i]) puts("AYE");
		else puts("NAY"); 
	return 0;
}

聪聪可可

依然没有什么区别,不过这次我们只关心路径长度\(\%3\)的余数:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,k,rt,sz,mmx,mxk,ans=0,vis[N],siz[N],cnt,first[N],w[3];
struct node{
	int v,w,nxt;
}e[N<<1];
inline void add(int u,int v,int w){e[++cnt].v=v;e[cnt].w=w;e[cnt].nxt=first[u];first[u]=cnt;}
inline void getsiz(int u,int f){
	siz[u]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;
		siz[u]+=siz[v];
	}
	return ;
}
int dis[N],d[N];
inline void findrt(int u,int f,int sum){
	int mx=sum-siz[u];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;	
		findrt(v,u,sum);
		mx=max(mx,siz[v]);
	}
	if(mx<mmx) mmx=mx,rt=u;
}
inline void getdis(int u,int f){
	dis[++sz]=d[u];
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||vis[v]) continue;
		d[v]=d[u]+e[i].w;
		getdis(v,u);
	}
}
inline void calc(int u){
	vector<int> used; 
	w[0]=1;w[1]=w[2]=0;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		sz=0;d[v]=e[i].w;
		getdis(v,u);
		for(int j=1;j<=sz;++j) ans+=w[(3-(dis[j]%3))%3];
		for(int j=1;j<=sz;++j) w[dis[j]%3]++;
	}
}
inline void solve(int u){
	rt=0;mmx=0x3f3f3f3f;sz=0;
	getsiz(u,0);
	findrt(u,0,siz[u]);
	vis[rt]=1;
	calc(rt);
	for(int i=first[rt];i;i=e[i].nxt){
		int v=e[i].v;
		if(vis[v]) continue;
		solve(v);
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1,u,v,w;i<n;++i){
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
	solve(1);
	int fz=ans*2+n,fm=n*n,g=__gcd(fz,fm);
	printf("%d/%d\n",fz/g,fm/g);
	return 0;
}

至此,大家大概已经理解点分治的原理和实现了,接下来我们再介绍一下点分树,又叫动态点分治:

未完待续。。。

标签:rt,分树,重心,int,siz,分治,笔记,vis,first
来源: https://www.cnblogs.com/tqxboomzero/p/14238684.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有