其他分享
首页 > 其他分享> > 基础长链剖分

基础长链剖分

作者:互联网

基础长链剖分

基本上整个互联网上长链剖分都是使用 CF1009F 和树上 \(k\) 级祖先两题。本篇也无法避免qwq,因为这两题确实经典。

定义

定义 重儿子 表示其子节点中子树深度最大的子节点。如果没有儿子,就没有重儿子。定义 轻儿子 表示剩余的子节点。重边轻边重链的定义和重链剖分相同。

然后如果你树形 \(dp\) 入门并知晓重链剖分了的话,相信求出一颗树的长链剖分并无难度。不如直接看题目。

经典题目

CF1009F Dominant Indices

给定一棵以 \(1\) 为根,\(n\) 个节点的树。设 \(d(u,x)\) 为 \(u\) 子树中到 \(u\) 距离为 \(x\) 的节点数。

对于每个点,求一个最小的 \(k\),使得 \(d(u,k)\) 最大。

定义 \(dp_{i,j}\) 表示 \(i\) 节点子树中与 \(i\) 的距离为 \(j\) 的节点个数。

\[dp_{u,i}=\sum_{v=son_u}dp_{v,i-1} \]

对于每个点有 \(dp_{i,0}= 1\)。暴力 \(dp\) 复杂度 \(O(n^2)\)。可以使用长链剖分优化至 \(O(n)\)。

长链剖分优化 \(dp\) 的一个基本思路是,每次转移继承重儿子的 \(dp\) 数组和答案,然后将轻儿子的 \(dp\) 数组暴力和当前节点的 \(dp\) 数组合并。

因为轻儿子的 \(dp\) 数组长度为轻儿子所在的重链长度,而所有重链长度和为 \(n\),所以暴力合并轻儿子的时间复杂度为 \(O(n)\)。

为了在合理的空间内实现这一操作,我们需要一点点指针的技巧,为 DP 数组的一整条重链分配内存,链上不同的节点之间有不同的首位置指针。(具体见代码)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;

const int N=1000006;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
vector<int>edge[N];
int n,fat[N],mxd[N],son[N],ans[N];
int usf[N];
int *dp[N],*cur=usf;
void dfs1(int u,int f){
	fat[u]=f;
	for(auto v:edge[u]){
		if(v==f)continue;
		dfs1(v,u);
		if(mxd[v]>mxd[son[u]])son[u]=v;
	}
	mxd[u]=mxd[son[u]]+1;
}
void dfs2(int u){
	//printf("dfs2 %d\n",u);
	dp[u][0]=1;
	if(!son[u]){ans[u]=0;return;}
	dp[son[u]]=dp[u]+1;//是 u 借用 son[u] 的信息哦!
	dfs2(son[u]);
	ans[u]=ans[son[u]]+1;
	for(auto v:edge[u]){
		if(v==son[u]||v==fat[u])continue;
		dp[v]=cur;cur+=mxd[v];
		dfs2(v);
		for(int i=1;i<=mxd[v];++i){
			dp[u][i]+=dp[v][i-1];
			if(dp[u][i]>dp[u][ans[u]]||(dp[u][i]==dp[u][ans[u]]&&i<ans[u]))
				ans[u]=i;
		}
	}
	if(dp[u][ans[u]]==1)ans[u]=0;//注意特判
}
int main(){
	n=read();
	for(int i=1;i<n;++i){
		int u=read(),v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	dfs1(1,0);
	dp[1]=cur,cur+=mxd[1];
	dfs2(1);
	for(int i=1;i<=n;++i)
		printf("%d\n",ans[i]);
	return 0;
}

LG5903 【模板】树上 k 级祖先

咱也不是发明这个算法的人,思考过程也并不清楚,就直接说流程就好了。

结论:一个节点的 \(k\) 级祖先所在的长链长大于等于 \(k\)。

根据长链剖分的性质真的真的不难得出。

预处理:

长链剖分。对于每条重链的根节点 \(u\),假设这条重链的长度为 \(d=mxd_u-dep_u+1\),求出 \(u\) 的 \([0,d-1]\) 级祖先和 \(u\) 和这条链上的 \([0,d-1]\) 级儿子(这个实际上就是求出每个儿子在重链的第几个)。时间复杂度 \(O(n)\)。(\(\sum d=n\))

倍增,预处理每个节点的 \(2^i\) 级祖先。时间复杂度 \(O(n\log n)\)。

查询:

记录 \(u\) 的 \(k\) 级祖先为 \(fat(u,k)\)。求出 \(i\),使得 \(2^i<k<2^{i+1}\)(预处理 \(\log\),可做到 \(O(1)\))。然后 \(u\gets fat(u,2^i),k\gets k-2^i\)。发现 \(fat(u,2^i)\) 所在的长链至少有 \(2^i\) 个节点,又 \(k-2^i< 2^i\leq d\),所以从这条长链顶点出发的所有 \(2^i\) 位置均被预处理了,可以 \(O(1)\) 求出。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <vector>
using namespace std;
typedef unsigned int ui;
typedef long long ll;
ui s;
inline ui get(ui x) {
	x ^= x << 13;
	x ^= x >> 17;
	x ^= x << 5;
	return s = x; 
}
const int N=1000006;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||'9'<ch){if(ch=='-')f=-1;ch=getchar();}
	while('0'<=ch&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
	return x*f;
}
int n,q,root,lg2[N],dep[N],mxd[N],son[N],tps[N],fa[N][23];
vector<int>edge[N],up[N],dw[N];
void dfs1(int u){
	mxd[u]=dep[u]=dep[fa[u][0]]+1;
	for(auto v:edge[u]){
		for(int i=0;fa[v][i];++i)
			fa[v][i+1]=fa[fa[v][i]][i];
		dfs1(v);
		if(mxd[v]>mxd[u])mxd[u]=mxd[v],son[u]=v;
	}
}
void dfs2(int u,int t){
	tps[u]=t;
	if(u==t){
		for(int i=0,v=u;i<=mxd[u]-dep[u];++i,v=fa[v][0])
			up[u].push_back(v);
		for(int i=0,v=u;i<=mxd[u]-dep[u];++i,v=son[v])
			dw[u].push_back(v);
	}
	if(son[u])dfs2(son[u],t);
	for(auto v:edge[u])
		if(v!=son[u])dfs2(v,v);
}
inline int qrkfa(int u,int k){
	//printf("%d %d\n",u,k);
	if(!k)return u;
	u=fa[u][lg2[k]];k-=(1<<lg2[k]);
	//printf(" %d %d\n",u,k);
	k-=(dep[u]-dep[tps[u]]);u=tps[u];
	return k>=0?up[u][k]:dw[u][-k];
}
ll ans;ui lasans;
int main(){
	n=read(),q=read();scanf("%u",&s);
	lg2[0]=-1;
	for(int i=1;i<=n;++i){
		fa[i][0]=read();
		if(!fa[i][0])root=i;
		else edge[fa[i][0]].push_back(i);
		lg2[i]=lg2[i>>1]+1;
	}
	dfs1(root);dfs2(root,root);
	for(int i=1,x,k;i<=q;++i){
		x=(get(s)^lasans)%n+1;
		k=(get(s)^lasans)%dep[x];
		ans^=1ll*i*(lasans=qrkfa(x,k));
		//printf("%d\n",lasans);
	}
	printf("%lld\n",ans);
	return 0;
}

写得好敷衍啊(不是

标签:长链,剖分,int,基础,son,mxd,include,节点,dp
来源: https://www.cnblogs.com/BigSmall-En/p/16592612.html