所谓长链剖分,就是对长链进行剖分
(逃)
前言
很优雅的算法
利用对指针进行魔法操作将 n2n^2n2 的 dp 优化成线性
线性啊!!!
解析
CF1009F Dominant Indices
给定一棵以 111 为根,nnn 个节点的树。设 d(u,x)d(u,x)d(u,x) 为 uuu 子树中到 uuu 距离为 xxx 的节点数。
对于每个点,求一个最小的 kkk,使得 d(u,k)d(u,k)d(u,k) 最大。
长剖板子题
设 fx,df_{x,d}fx,d 表示x子树内到x距离为d的结点个数
则有:
fx,d←fson,d−1f_{x,d}\gets f_{son,d-1}fx,d←fson,d−1
所以其实就是下标偏离了一位而已
那么如何解决呢?
长链剖分
定义一个子树的深度是子树内深度最大的儿子的深度
定义一个结点的重儿子是子树深度最大的儿子
和重链剖分类似的,重儿子连成重链,单独结点也算一个重链,每个结点都在唯一的重链上
void dfs1(int x,int fa){for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==fa) continue;dfs1(to,x);if(1) son[x]=to;}len[x]=len[son[x]]+1;
}
指针偏移
既然我们的父亲dp值只是和儿子相比偏移了一位
那么我们就尝试利用指针的便宜让父亲直接继承儿子的dp
当然,继承所有儿子的dp是无法实现的,那么我们就让父亲继承重儿子的dp值,其他儿子暴力转移
void dp(int x,int fa){f[x][0]=1;if(son[x]){f[son[x]]=f[x]+1;dp(son[x],x);ans[x]=ans[son[x]]+1;}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==fa||to==son[x]) continue;f[to]=now;now+=len[to];dp(to,x);for(int j=0;j<len[to];j++){f[x][j+1]+=f[to][j];if(f[x][j+1]>f[x][ans[x]]||(f[x][j+1]==f[x][ans[x]]&&j+1<ans[x])) ans[x]=j+1;}}if(f[x][ans[x]]==1) ans[x]=0;
}
复杂度证明
每个重链从链头向父亲转移,会产生重链长度的代价
由于重链长度和为结点个数,所以复杂度为 O(n)O(n)O(n)
注意! 和重链剖分不同,每个结点到根节点的长链个数可以达到 O(n)O(\sqrt n)O(n) 级别
完整代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
const int N=1e6+100;
const double eps=1e-12;
inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n;
struct node{int to,nxt;
}p[N<<1];
int fi[N],cnt;
inline void addline(int x,int y){p[++cnt]=(node){y,fi[x]};fi[x]=cnt;return;
}
int son[N],len[N];
int buf[N],*f[N],*now=buf;
void dfs(int x,int f){for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==f) continue;dfs(to,x);if(len[to]>len[son[x]]) son[x]=to;}len[x]=len[son[x]]+1;
}
int ans[N];
void dp(int x,int fa){f[x][0]=1;if(son[x]){f[son[x]]=f[x]+1;dp(son[x],x);ans[x]=ans[son[x]]+1;}for(int i=fi[x];~i;i=p[i].nxt){int to=p[i].to;if(to==fa||to==son[x]) continue;f[to]=now;now+=len[to];dp(to,x);for(int j=0;j<len[to];j++){f[x][j+1]+=f[to][j];if(f[x][j+1]>f[x][ans[x]]||(f[x][j+1]==f[x][ans[x]]&&j+1<ans[x])) ans[x]=j+1;}}if(f[x][ans[x]]==1) ans[x]=0;
}
signed main(){
#ifndef ONLINE_JUDGE//freopen("a.in","r",stdin);//freopen("a.out","w",stdout);
#endifmemset(fi,-1,sizeof(fi));cnt=-1;n=read();for(int i=1;i<n;i++){int x=read(),y=read();addline(x,y);addline(y,x);}dfs(1,0);f[1]=now;now+=len[1];dp(1,0);for(int i=1;i<=n;i++) printf("%d\n",ans[i]);return 0;
}
/**/
Thanks for reading!