基础长链剖分
基本上整个互联网上长链剖分都是使用 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 From: https://www.cnblogs.com/BigSmall-En/p/16592612.html