长链剖分

长链剖分也属于树链剖分的一种
一般讲的树剖都指轻重链剖分,它可以用于维护树上路径的信息
而长链剖分则是用于维护有关深度的信息


剖分方法

长链剖分的剖分方法与轻重链剖分极其相似
只需要把以子树大小判断重儿子改成以节点深度判断即可

void dfs1(int u,int pa)
{
	dep[u]=mxd[u]=dep[pa]+1;//mxd是该节点出发能到的最大深度
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==pa) continue;
		dfs1(v,u);
		if(mxd[v]>mxd[u]) mxd[u]=mxd[v],son[u]=v; 
	}
}

void dfs2(int u,int tp)
{
	top[u]=tp;
	if(son[u]) dfs2(son[u],tp);
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==son[u]||v==fa[u][0]) continue;
		dfs2(v,v);
	}
}


长链剖分性质

性质1:长链剖分后,所有节点都仅属于一条链
性质2:任意节点u的第k级祖先v所在链的长度一定大于k

证明:
若u,v在一条链上,那么显然链长大于k
若u,v不在一条链上,假设v所在链长度小于k,那么u~v的链长显然更长,u,v应在一条链上,矛盾

性质3:任意节点到达根节点经过的长链数是 n \sqrt{n} n 级的

由性质2,每次跳跃到的新链长度不会小于当前链
最坏的情况,长链长度为1,2,3,…, n \sqrt{n} n 单调递增


应用

树上k级祖先

洛谷P5903 【模板】树上 k 级祖先
比较经典的应用
用树上倍增解决的话可以O(nlogn)预处理,O(logn)回答
而长链剖分可以O(nlogn)预处理,O(1)回答

先预处理出树上倍增数组fa[u][i],表示u的第2^i级祖先
并预处理出 1 ~ n 每个数二进制下最高位的1的位置,即highbit(k),以下记 h k h_k hk

长链剖分后,对于每条链,如果其长度为 len
那么在顶点处记录顶点向上的len个祖先和向下的len个链上的儿子

对于每次u,k的询问
先利用倍增数组将u跳到u的第 2 h k 2^{h_k} 2hk级祖先,设剩下 k ′ k' k级,显然 k ′ < 2 h k k'<2^{h_k} k<2hk
而此时u所在长链长度>= 2 h k 2^{h_k} 2hk>k’

所以再将u跳到链顶top[u]
之后剩下的级数直接在预处理出的向上向下的数组里O(1)查询即可

#include<iostream>
#include<cstdio>
#include<cmath>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
typedef unsigned int ui;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=500010;
ui s;
int n,Q,rt;
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int hi[maxn];
int fa[maxn][25],dep[maxn],mxd[maxn];
int son[maxn],top[maxn];
vector<int> pre[maxn],nxt[maxn];
lt ans[maxn*10];

ui get(ui x) 
{
	x^=x<<13;
	x^=x>>17;
	x^=x<<5;
	return s=x; 
}

void add(int u,int v)
{
	E[++tot].nxt=head[u];
	E[tot].v=v;
	head[u]=tot;
}

void dfs1(int u,int pa)
{
	dep[u]=mxd[u]=dep[pa]+1;
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==pa) continue;
		dfs1(v,u);
		if(mxd[v]>mxd[u]) mxd[u]=mxd[v],son[u]=v; 
	}
}

void dfs2(int u,int tp)
{
	top[u]=tp;
	if(u==tp)
	{
		int x=u;
		for(int i=0;i<=mxd[u]-dep[u];++i)
		{
			pre[u].push_back(x);
			x=fa[x][0];
		}
		x=u;
		for(int i=0;i<=mxd[u]-dep[u];++i)
		{
			nxt[u].push_back(x);
			x=son[x];
		}
	}
	
	if(son[u]) dfs2(son[u],tp);
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==son[u]||v==fa[u][0]) continue;
		dfs2(v,v);
	}
}

int solve(int u,int k)
{
	if(k==0) return u;
	u=fa[u][hi[k]];
	k-=1<<hi[k];
	k-=dep[u]-dep[top[u]];
	u=top[u];
	return k>=0?pre[u][k]:nxt[u][-k];
}

int main()
{
	n=read(); Q=read(); s=read();
	
	hi[0]=-1;
	for(int i=1;i<=n;++i)
	{
		fa[i][0]=read();
		add(fa[i][0],i); add(i,fa[i][0]);
		if(fa[i][0]==0) rt=i;
		hi[i]=hi[i>>1]+1;
	}
	
	dfs1(rt,0); dfs2(rt,rt);
	for(int i=1;(1<<i)<=n;++i)
	for(int u=1;u<=n;++u)
	fa[u][i]=fa[fa[u][i-1]][i-1];
	
	for(int i=1;i<=Q;++i)
	{
		int x=(get(s)^ans[i-1])%n+1;
		int k=(get(s)^ans[i-1])%dep[x];

		ans[i]=solve(x,k);
	}
	
	lt res=ans[1];
	for(int i=2;i<=Q;++i)
	res^=(lt)i*ans[i];
	
	printf("%lld",res);
	return 0;
}

与深度有关的DP

CodeForces - 1009F Dominant Indices

题目大意: 给定一棵以1为根,n个节点的树。设 d(u,x)为u子树中到u距离为x的节点数。
对于每个点,求使得d(u,x)最大的x,若有多个则输出最小的x

题目分析:
设dp[u][k]表示u子树中与u距离为k的节点数,
则 dp[u][k]=Σdp[v][k-1] (v为u的儿子)

设mxlen[u]表示u到最远的叶子结点的距离
每次转移时,可以先选择任意一个儿子v,把dp[v][]直接错位复制给dp[u][]
可通过数组的指针操作( dp[u]=dp[v]+1 )以O(1)实现

之后其他儿子的信息暴力合并到dp[u]
即对每个儿子v,按照上述转移方程进行mxlen[v]次更新

可以发现这样做的复杂度直接由每次暴力合并的mxlen决定
如果我们把第一次直接复制的儿子选为长链剖分的重儿子
那么暴力更新的mxlen就取得最小值,总复杂度就变成了O(n)

还有一点要注意的是dp数组显然不能开dp[1e6][1e6]那么大
但由于所有长链长度和就是n,所以可以用指针指向一个大小为n的数组动态使用空间

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

int read()
{
	int f=1,x=0;
	char ss=getchar();
	while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
	while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
	return f*x;
}

const int maxn=1000010;
int n;
struct node{int v,nxt;}E[maxn<<1];
int head[maxn],tot;
int son[maxn],mxlen[maxn];
int buf[maxn];
int *dp[maxn],*cur=buf;
int ans[maxn];

void add(int u,int v)
{
	E[++tot].nxt=head[u];
	E[tot].v=v;
	head[u]=tot;
}

void dfs1(int u,int pa)
{
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==pa) continue;
		dfs1(v,u);
		if(mxlen[v]>mxlen[son[u]]) son[u]=v;
	}
	mxlen[u]=mxlen[son[u]]+1;
}

void dfs2(int u,int pa)
{
	dp[u][0]=1;
	if(son[u])
	{
		dp[son[u]]=dp[u]+1;//错位更新信息
		dfs2(son[u],u);
		ans[u]=ans[son[u]]+1;
	}
	for(int i=head[u];i;i=E[i].nxt)
	{
		int v=E[i].v;
		if(v==son[u]||v==pa) continue;
		
		dp[v]=cur; cur+=mxlen[v];
		dfs2(v,u);
		for(int j=1;j<=mxlen[v];++j)//暴力更新
		{
			dp[u][j]+=dp[v][j-1];
			if(dp[u][j]>dp[u][ans[u]]) ans[u]=j;
			else if(dp[u][j]==dp[u][ans[u]] && j<ans[u]) ans[u]=j;
		}
	}
	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();
		add(u,v); add(v,u);
	}
	
	dfs1(1,0); 
	
	dp[1]=cur; cur+=mxlen[1];
	dfs2(1,0);
	
	for(int i=1;i<=n;++i)
	printf("%d\n",ans[i]);
	
	return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值