手把手教你点分 点分树

点分

•先看一道题

•P3806 给定一棵有 n 个点的树,询问树上距离为 k 的点对是否存在。
•N<=10000

•相信聪明的你已经想到了!n 遍dfs嘛!

•但是过不了

那我们来看看点分怎么做

•什么是点分治?

•点分治是处理树上问题的一种高效的办法,时间复杂度很优秀,而且思想比较巧妙。

•首先我们引入树的重心

•树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡。

•什么意思呢?

•我们可以从这里推得以树的重心为根的任意一颗子树大小不超过n/2

•怎么求呢?

•直接由定义dfs就好了

•看代码

在这里插入图片描述
•我们只需要不断寻找重心,用这些重心来计算我们要的答案

•我们举个例子

•首先我们可以找到一个重心 并且计算经过重心的所有路径对答案的贡献

•通常是遍历子树来求解

•这道题 求是否有长度为k的路径来讲 就是以重心为起点 记录到子树每个点中的长度

•如果能用桶就直接搞 如果不能就用 m a p map map 每找到一条距重心长度为 x x x的点 就看一下之前有没有找到距重心长度为 k − x k-x kx的点 注意两个点不能再重心的同一个儿子的子树内

•经过这个重心的路径贡献就统计了

•接着重心把原树分成了多个子树

•就分治下去 计算子树的贡献

•同样再找子树重心 统计贡献 再分治……以此类推

•因为我们可以从这里推得以树的重心为根的任意一颗子树大小不超过n/2

•每次树的大小会变得不超过原来的一半, l o g 2 log_2 log2N次后树的大小就会变成1,每一次最多遍历整棵树 O ( N ) O(N) O(N),复杂度就得到 O ( N l o g N ) O (NlogN) O(NlogN)

点分树

•点分树是以点分治为基础的,把原树“化实为虚”的构造。

•我们在点分治的过程中,存储每个分治重心的上级重心,也就是点分树的父子关系。

•显然,根据点分治的原理,点分树的树高是 l o g n logn logn的。这样,我们可以利用这棵树来跑一些类似“从询问点出发,不断跳 f a fa fa的暴力,来解决一些树上的多次询问/修改问题。

拿一棵树来模拟一下建点分树

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
•看题

•P6329给一个带点权的树,要求在线处理
•0 x k 与节点x的距离小于等于k的点的点权和
•1 x y 节点x的点权变成y
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105

•建出点分树来,每个点维护一个 vector 作为桶,维护点分树上子树内到当前点距离为 k 的点权和。即vector下标为k,值为权值和。每次求vector时,dfs遍历子树即可

•对于子树内的答案就是求vector的前缀和。

•这样对于询问,每次只需要跳点分树,然后对于每个点分树上的 fa 统计距离 f a fa fa k − d i s ( f a , x ) k-dis(fa,x) kdis(fa,x) 的点的贡献就好了。( f a fa fa x x x d i s ( f a , x ) dis(fa,x) dis(fa,x)这么远,那么能贡献的点距离 f a fa fa k − d i s ( f a , x ) k-dis(fa,x) kdis(fa,x)远)

•但是还有一个问题,就是对于以当前 f a fa fa 为根的那些子树,在算下一个 f a fa fa 的时候会被算重。

•于是就要再维护一个桶,表示 x x x 点分子树内的点,到点分树上 x x x 的父亲的距离为 k k k 的点权和。由于边权都为 1 ,这个操作就会很方便。

•考虑如果带修改,那无非就是把桶换成树状数组即可。

•建树时每一个点最多有 l o g n logn logn个祖先 所以最多被访问 l o g n logn logn

•对于这道题而言 因为使用了树状数组 所以多一个log

•建树的时间 O ( n l o g 2 n ) O(nlog^2 n) O(nlog2n)

•怎么修改呢?

•假设我们修改的点为x

•每次暴力跳点分树上每一个祖先fa

•把x对fa的贡献修改了

•所以每一次修改要跳 l o g n logn logn次祖先 修改贡献需要 O ( l o g n ) O(logn) O(logn)

•修改的时间 O ( l o g 2 n ) O(log^2 n) O(log2n)

•那么求解答案的时候,同样要跳 l o g n logn logn次祖先

•每一次求子树内贡献需要 O ( l o g n ) O(logn) O(logn)

•求解答案的时间 O ( l o g 2 n ) O(log^2 n) O(log2n)

再看一道题

P2056 [ZJOI2007]捉迷藏

Jiajia 和 Wind 是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind 和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由 N 个屋子和 N−1 条双向走廊组成,这 N−1 条走廊的分布使得任意两个屋子都互相可达。

游戏是这样进行的,孩子们负责躲藏,Jiajia 负责找,而 Wind 负责操纵这 N 个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia 希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。

我们将以如下形式定义每一种操作:

- C(hange) i 改变第 i 个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
- G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

显然建点分树啦

我们每次开关一盏灯的时候会影响到点分树上一条链的信息。直接跳即可。

考虑维护答案,我们开三个大根堆,一个维护子树内最长链,一个维护到父节点的最长链,还有一个维护所有的答案。

维护所有答案的堆注意随时更新

统计答案的时候每个子树只用取最大值次大值即可。

总体复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),足以通过本题。

#include<bits/stdc++.h>
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(fast)
#define inf 1e9
#define N 100100
using namespace std;
inline char get_char(){
	static char buf[1000000],*p1,*p2;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    int numm=0;
    char _;_=get_char();
    while(_<'0'||_>'9'){_=get_char();} 
    while(_>='0'&&_<='9'){numm=(numm<<3)+(numm<<1)+(_^48);_=get_char();   }
    return numm;
}
int n,f1[N],nxt1[N*2],data1[N*2],num,now,siz[N];
int f[N],nxt[N*2],data[N*2],dep[N];
int fa[N],dis[N];
bool vis[N],lt[N];
vector<int> disnow[N];
struct node{
	priority_queue<int> x,y;
	inline void push(int a){x.push(a);}
    inline void del(int a){y.push(a);}
    inline int top(){while(y.size()&&x.top()==y.top())x.pop(),y.pop();return x.top();}
    inline int size(){return x.size()-y.size();}
    inline void pop(){while(y.size()&&x.top()==y.top())x.pop(),y.pop();x.pop();}
    inline int sectop(){int a=top();pop();int b=top();push(a);return b;} 
	inline void clear(){while(x.size())x.pop();while(y.size())y.pop();}
}ans,far[N],ma[N];
inline void ans_push(node &x){
	if(x.size()>=2)ans.push(x.top()+x.sectop());
}
inline void ans_del(node &x){
	if(x.size()>=2)ans.del(x.top()+x.sectop());
}
void add(int x,int y){
	nxt1[++num]=f1[x];
	f1[x]=num;
	data1[num]=y;
}
void add2(int x,int y){
	nxt[++num]=f[x];
	f[x]=num;
	data[num]=y;
}
void find_rt(int x,int &root,int sum,int pp){
	siz[x]=1;
	int y,maxx=0;
	for(int i=f1[x];i;i=nxt1[i]){
		y=data1[i];
		if(y==pp||vis[y])continue;
		find_rt(y,root,sum,x);
		siz[x]+=siz[y];
		maxx=max(maxx,siz[y]);
	}
	maxx=max(siz[x],sum-siz[x]);
	if(maxx<now){
		now=maxx;root=x;
	}
}
void get_far(int s,int x,int y,int pp){
	far[0].push(y);
	disnow[x].push_back(y);
	siz[x]=1;
	int v;
	for(int i=f1[x];i;i=nxt1[i]){
		v=data1[i];
		if(v==pp||vis[v])continue;
		get_far(s,v,y+1,x);
		siz[x]+=siz[v];
	}
}
void build(int x,int pp){
	vis[x]=1;
	int y,root;
	for(int i=f1[x];i;i=nxt1[i]){
		y=data1[i];
		if(vis[y])continue;
		far[0].clear();
		get_far(pp,y,1,x);
		now=inf;
		find_rt(y,root,siz[y],-1);
		far[root]=far[0];fa[root]=x;dep[root]=dep[x]+1;
		add2(x,root);
		if(far[root].size())ma[x].push(far[root].top());
		build(root,x);
	}
	ma[x].push(0);
	ans_push(ma[x]); 
}

void work1(int x){
	int xx=x,pp,tmp=1;
	ans_del(ma[x]);
	ma[x].del(0);
	
	while(fa[xx]){
		pp=fa[xx];
		ans_del(ma[pp]);
		ma[pp].del(far[xx].top());
		xx=pp;
	}
	xx=x;
	ans_push(ma[x]);
	
	while(fa[xx]){
		pp=fa[xx];
		tmp=disnow[x][dep[pp]];
		far[xx].del(tmp);
		if(far[xx].size())
		ma[pp].push(far[xx].top());
		ans_push(ma[pp]);
		xx=pp;
	}
}

void work2(int x){
	int xx=x,pp,tmp=1;
	
	ans_del(ma[x]);
	while(fa[xx]){
		pp=fa[xx];
		ans_del(ma[pp]);
		if(far[xx].size())
		ma[pp].del(far[xx].top());
		xx=pp;
	}
	xx=x;
	ma[x].push(0);
	ans_push(ma[x]);
	while(fa[xx]){
		pp=fa[xx];
		tmp=disnow[x][dep[pp]];
		far[xx].push(tmp);
		ma[pp].push(far[xx].top());
		ans_push(ma[pp]);
		xx=pp;
	}
}

int main(){
//	freopen("test.txt","r",stdin);
	n=read();
	
	int a,b;
	for(int i=1;i<n;i++){
		a=read();b=read();
		add(a,b);add(b,a);
	}
	num=0;
	int root;
	now=inf;
	find_rt(1,root,n,-1);
	build(root,-1);
	int Q; char ss='0';
	Q=read();
	while(Q--){
		ss=get_char();
		while(ss!='G'&&ss!='C')ss=get_char();
//		cout<<ss<<' ';
		if(ss=='G'){
			if(ans.size()) printf("%d\n",ans.top());
			else printf("-1\n"); 
		}
		else{
			a=read();
//			cout<<a<<endl;
			if(!lt[a]){
				work1(a);
			}
			else work2(a);
			lt[a]^=1;
		}
	}
} 

本来说的是七月份开始经常写博客的
哎七月都20号了 我怎么这么菜

posted @ 2022-10-10 20:19  缙云山车神  阅读(131)  评论(0编辑  收藏  举报