长链剖分初学笔记
长链剖分是重链剖分的兄弟,但与CZ宇宙里的兄弟不同的是,他不止三厘米,相反,他是越长越好,越长越好。长链剖分的原理是在统计信息时父亲直接继承某个孩子信息,其它孩子则暴力统计。这个孩子就是长孩子,连接它和父亲之间的边就叫长链。长孩子的定义是从这个孩子向下可以到达最深的角落,从这个定义出发可以想到这个东西的使用场景,即要求快速统计有关深度的一些信息。
长链剖分核心是长链,而使用长链剖分可以把算法的复杂度降低到\(O(NlogN)\)甚至到恐怖的\(O(N)\)。原理上面也说过了,那就是父亲继承(那玩意似乎可以总结为父继子业?很是有点大逆不道)长孩子的信息。怎么继承?既然是在统计和深度有关的内容,那么可以想到一个深深的节点,到它孩子的距离比到它的距离少了一个1(或者边长),那么把孩子内所有结点的距离都加上那个值不就是父亲的答案了吗?于是也不知道哪个天才想出了用指针解决“快速加同值”的问题……
然后就可以啦。代码实现上,口诀是统计自己,统计长孩子,关照其它孩子并给长孙子留出dp空间。注意一下就是非长儿子转移到自己时要清晰哪些dp值是能用的。
最后就是这玩意似乎不支持在线,而且要在solve中记录答案,因为数组就那么大,你不记录的话别人一调用信息不久乱套了吗……
#include<cstdio>
//#define zczc
const int N=1000010;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w<='9'&&w>='0'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
int m;
struct edge{
int t,next;
}e[N<<1];
int head[N],esum;
inline void add(int fr,int to){
esum++;
e[esum].t=to;
e[esum].next=head[fr];
head[fr]=esum;
return;
}
int son[N],d[N];
void dfs(int wh,int fa){
d[wh]=1;
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(th==fa)continue;
dfs(th,wh);
if(d[th]+1>d[wh])d[wh]=d[th]+1,son[wh]=th;
}
}
int *f[N],data[N],ans[N];int *p=data;
void solve(int wh,int fa){
f[wh][0]=1;
if(son[wh]!=0){
f[son[wh]]=f[wh]+1;
solve(son[wh],wh);
ans[wh]=ans[son[wh]]+1;
}
for(int i=head[wh],th;i;i=e[i].next){
th=e[i].t;
if(th==fa||th==son[wh])continue;
f[th]=p;p+=d[th];
solve(th,wh);
for(int i=0;i<d[th];i++){
f[wh][i+1]+=f[th][i];
if(f[wh][i+1]>f[wh][ans[wh]])ans[wh]=i+1;
else if(f[wh][i+1]==f[wh][ans[wh]]&&i+1<ans[wh])ans[wh]=i+1;
}
}
if(f[wh][ans[wh]]==1)ans[wh]=0;
return;
}
signed main(){
#ifdef zczc
freopen("in.txt","r",stdin);
#endif
int s1,s2;
read(m);
for(int i=1;i<m;i++){
read(s1);read(s2);
add(s1,s2);add(s2,s1);
}
dfs(1,0);
f[1]=p;p+=d[1];
solve(1,0);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}