长链剖分也属于树链剖分的一种
一般讲的树剖都指轻重链剖分,它可以用于维护树上路径的信息
而长链剖分则是用于维护有关深度的信息
剖分方法
长链剖分的剖分方法与轻重链剖分极其相似
只需要把以子树大小判断重儿子改成以节点深度判断即可
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;
}