点分治&动态点分治学习笔记

点分治&动态点分治学习笔记

点分治

在学习点分治之前需要了解中点分治,我们在处理每一个区间的信息之和的时候,需要通过分治的方式快速的解决,时间复杂度是 \(O(n\log n)\) 的,十分优秀。而在树上的问题,我们也可以使用对应的方式,即点分治。而对于一个树上的中点可以理解为树的重点,每次搜索时,都直接搜到下一个节点所包含的树的重心,这样的好处时显而易见的——dfs搜索树的深度肯定是 \(\log n\) 级别的。做一些在一般的树上不行的暴力就是可以的。当然有些操作是不能做的。

先给出例题:

P3806 【模板】点分治 1

我们发现我们每一次跳一个子树的重心,然后再从重心往子树其他的结点跑,再记录贡献,看似非常暴力,但是因为重心的特殊性质每一个点至多只被经过 \(\log n\) 次,时间复杂度均摊下来就是 \(O(n\log n)\)P4178 Tree - 洛谷 (luogu.com.cn)

这题就是直接上面用桶可以维护的内容使用树状数组来维护即可,不做赘述。

CF914E Palindromes in a Tree - 洛谷 (luogu.com.cn)

这题稍微有点难度,将问题转化一下,就是找到一个路径使得路径中出现奇数个的至多只有一个,发现刚好字符数只有 \(20\) 个,刚好可以状压压掉,然后判断方案数时,把 \(1\) 出现的数量小于等于 \(1\) 的都加上即可。

点分树

这里的点分树就是动态的点分治,就是再构建一个树,是根据点分治的 dfs 顺序建的树。这颗树很有性质:

  • 树高是 \(O(\log n)\) 级别。
  • \(\sum siz_i \sim O(n\log n)\)

这就让我们的暴力更加优雅了。

P6329 【模板】点分树 | 震波 - 洛谷 (luogu.com.cn)

这题建完树之后修改就是对每一个点及其祖先更改贡献,注意可能重复贡献,记得多写一个线段树记多的贡献,需要动态开点线段树,常数需要写的小一点,不然会 T。代码中只是比点分治的版本多两个函数:

void change(int x,int y){
	int cur=x;
	while(cur){
		seg1.change(seg1.rt[cur],0,n,dist(cur,x),y);
		if(p[cur])seg2.change(seg2.rt[cur],0,n,dist(p[cur],x),y); 
		cur=p[cur];
	}
}
int query(int x,int k){
	int res=0,cur=x;
	while(cur){
		res+=seg1.query(seg1.rt[cur],0,n,0,k-dist(x,cur));
		if(p[cur])res-=seg2.query(seg2.rt[cur],0,n,0,k-dist(x,p[cur]));  
		cur=p[cur];
	}
	return res;
}

[P2056 ZJOI2007] 捉迷藏 - 洛谷 (luogu.com.cn)

这就是一个可以更改每一个点的状态,求端点都是亮的最长路径的长度。

其实可以直接动态直径,而且更快。然而这是动态点分治专题,我们需要更符合专题的解法。

梳理一下结构,我们需要所有的结点的最长链和次长链的最大值。而这里的最长链和次长链肯定是不在同一个子树里的,需要一个结构 \(B\)。那我们还需要再记一个结构 \(A\) 记录这个点的子树到他的父亲的距离。这里可删堆就很好。这里有一些细节,比如亮点可以在 \(B\) 中放一个 \(0\) 的长度。

点分治模板代码:

//点分治
void getroot(int u,int fa){
	siz[u]=1;mx[u]=0;
	for(auto tmp:e[u]){
		int v=tmp.first;
		if(v==fa || vis[v])continue;
		getroot(v,u);
		siz[u]+=siz[v];
		mx[u]=max(siz[v],mx[u]);
	}
	mx[u]=max(mx[u],sum-siz[u]);
	if(mx[u]<mx[rt])rt=u;
}
void dfs1(int u){
	vis[u]=d[0]=1;
	calc(u);
	for(auto tmp:e[u]){
		int v=tmp.first;
		if(vis[v])continue;
		sum=siz[v],mx[rt=0]=n;
		getroot(v,0),dfs1(rt);
	}
}
posted @ 2025-09-01 15:19  hnczy  阅读(20)  评论(1)    收藏  举报