長鏈剖分學習筆記
長鏈剖分
長鏈剖分用於優化一些特殊的dp,可以將某些\(O(n)\)的時間復雜度降為均攤\(O(1)\)。
感覺這玩意兒大部分東西都和樹鏈剖分挺像,理解的時候可以照著輕重鏈剖分那種去理解
定義
長鏈
和重鏈差不多,就是從某一個節點走到它子樹中最深的節點所經過的路徑
重兒子
某個節點在長鏈上的兒子就是它的重兒子(很好奇為什麽不叫長兒子)。
頂點
某條長鏈中深度最小的點就是該長鏈的頂點
不難看出長鏈剖分和樹鏈剖分其實很像,樹鏈剖分中重兒子所在子樹具有最大的size,而長鏈剖分中重兒子所在子樹具有最大的dep
性質
1.所有鏈長總和為\(O(n)\)
2.任意一個節點\(x\)的\(k\)級祖先\(y\)
正確性的話,自己yy一下好了
應用
因為這東西的基礎基本和樹剖沒什麽區別所以不多講了,還是從應用裏還說明它的一下特殊性好了
O(1)在線查詢某一個點的\(k\)級祖先
例題lxhgww的奇思妙想
我們設\(len[u]\)為\(u\)所在長鏈的長度,對於每一個長鏈的頂點,我們維護它的1到\(len[u]\)級兒子以及1到\(len[u]\)級祖先
同時預處理找祖先的倍增數組,以及\(1\)到\(n\)的每一個數字的二進制最高位即\(highbit\)
那麽對於每一個詢問\((u,k)\),我們設\(r=highbit(k)\),那麽我們用預處理的倍增數組讓\(u\)
因為\(k-r<r\),那麽\(v\)的長鏈的長度\(\geq r>k-r\),那麽\(v\)所在的長鏈預處理的表一定已經包含了\(u\)的\(k\)級祖先
時間復雜度為\(O(nlogn+m)\),預處理\(O(nlogn)\),每一次回答\(O(1)\)
//minamoto #include<iostream> #include<cstdio> #include<vector> using namespace std; #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) char buf[1<<21],*p1=buf,*p2=buf; int read(){ #define num ch-'0' char ch;bool flag=0;int res; while(!isdigit(ch=getc())) (ch=='-')&&(flag=true); for(res=num;isdigit(ch=getc());res=res*10+num); (flag)&&(res=-res); #undef num return res; } char sr[1<<21],z[20];int C=-1,Z; inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;} void print(int x){ if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x; while(z[++Z]=x%10+48,x/=10); while(sr[++C]=z[Z],--Z);sr[++C]='\n'; } const int N=3e5+5; int head[N],Next[N<<1],ver[N<<1],tot; inline void add(int u,int v){ ver[++tot]=v,Next[tot]=head[u],head[u]=tot; } int n,md[N],dep[N],fa[N][21],son[N],top[N],len[N],B[N]; vector<int> U[N],D[N]; void dfs(int u,int f){ md[u]=dep[u]=dep[f]+1,fa[u][0]=f; for(int i=1;i<20;++i) if(fa[u][i-1]) fa[u][i]=fa[fa[u][i-1]][i-1]; else break; for(int i=head[u];i;i=Next[i]){ int v=ver[i]; if(v!=f){ dfs(v,u); if(md[son[u]]<md[v]) son[u]=v,md[u]=md[v]; } } } void dfs2(int u,int t){ top[u]=t,len[u]=md[u]-dep[t]+1; if(son[u]){ dfs2(son[u],t); for(int i=head[u];i;i=Next[i]) if(!top[ver[i]]) dfs2(ver[i],ver[i]); } } void init(){ int now=0; for(int i=1;i<=n;++i){ if(!(i&(1<<now))) ++now; B[i]=now; } for(int i=1;i<=n;++i) if(i==top[i]){ for(int j=1,u=i;j<=len[i]&&u;++j) u=fa[u][0],U[i].push_back(u); for(int j=1,u=i;j<=len[i]&&u;++j) u=son[u],D[i].push_back(u); } } int query(int u,int k){ if(k>dep[u]) return 0;if(k==0) return u; u=fa[u][B[k]],k^=1<<B[k]; if(k==0) return u; if(dep[u]-dep[top[u]]==k) return top[u]; if(dep[u]-dep[top[u]]<k) return U[top[u]][k-dep[u]+dep[top[u]]-1]; else return D[top[u]][dep[u]-dep[top[u]]-k-1]; } int main(){ // freopen("testdata.in","r",stdin); n=read(); for(int i=1;i<n;++i){ int u=read(),v=read(); add(u,v),add(v,u); } dfs(1,0),dfs2(1,1),init(); int lastans=0,q=read(); while(q--){ int u=read()^lastans,v=read()^lastans; printf("%d\n",lastans=query(u,v)); } return Ot(),0; }
O(n)合並以深度為下標的信息
這個比較類似於dsu on tree了,對於一些和深度有關的信息,我們可以直接繼承它重兒子的信息,對於輕兒子的暴力統計。比方說\(f_{u,i}\)表示到\(u\)的子樹中到它的距離為\(i\)的點的個數,\(v\)為\(u\)的重兒子,那麽可以直接\(f_{u,i}=f_{v,i-1}\),然後輕兒子的暴力統計
時間復雜度證明的話,因為每個點只會暴力統計它的輕兒子,所以每一個點也只會在從它到根節點的第一條輕邊上被統計一遍,於是總的復雜度是\(O(n)\)
對於每個點,只需要開正比於它所在長鏈長度的空間,總的空間復雜度也是\(O(n)\)
具體實現的話,可以用指針來維護,每一次合並的之後只要把指針給移位即可
例題CF1009F Dominant Indices
給你一棵樹,定義\(d_{x,i}\)表示\(x\)子樹內和\(x\)距離為\(i\)的節點數,對每個\(x\)求使\(d_{x,i}\)最大的\(i\),如有多個輸出最小的。
首先我們得求出對於每個點,它子樹中距離它不同距離的點有多少,很顯然\(f_{u,i}=\sum f_{v,i-1}\)。然而時空都得炸。於是讓每個點繼承重兒子的信息,對輕兒子的信息暴力統計,對於每一條長鏈,發現開的數組最大是這條長鏈的長度,其他都沒有用。於是總的空間復雜度即為長鏈的長度之和,為\(O(n)\)
//minamoto
#include<bits/stdc++.h>
using namespace std;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
int read(){
int res,f=1;char ch;
while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
return res*f;
}
char sr[1<<21],z[20];int C=-1,Z=0;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
void print(int x){
if(C>1<<20)Ot();if(x<0)sr[++C]='-',x=-x;
while(z[++Z]=x%10+48,x/=10);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e6+5;
int head[N],Next[N<<1],ver[N<<1],tot;
inline void add(int u,int v){ver[++tot]=v,Next[tot]=head[u],head[u]=tot;}
int len[N],son[N],tmp[N],*f[N],*id=tmp,ans[N],n;
void dfs(int u,int fa){
for(int i=head[u];i;i=Next[i])if(ver[i]!=fa){
dfs(ver[i],u);
if(len[ver[i]]>len[son[u]])son[u]=ver[i];
}
len[u]=len[son[u]]+1;
}
void dp(int u,int fa){
f[u][0]=1;if(son[u])f[son[u]]=f[u]+1,dp(son[u],u),ans[u]=ans[son[u]]+1;
for(int i=head[u];i;i=Next[i]){
int v=ver[i];if(v==fa||v==son[u])continue;
f[v]=id,id+=len[v],dp(v,u);
for(int j=1;j<=len[v];++j){
f[u][j]+=f[v][j-1];
if((j<ans[u]&&f[u][j]>=f[u][ans[u]])||(j>ans[u]&&f[u][j]>f[u][ans[u]]))
ans[u]=j;
}
}
if(f[u][ans[u]]==1)ans[u]=0;
}
int main(){
// freopen("testdata.in","r",stdin);
n=read();
for(int i=1,u,v;i<n;++i)u=read(),v=read(),add(u,v),add(v,u);
dfs(1,0),f[1]=id,id+=len[1],dp(1,0);
for(int i=1;i<=n;++i)print(ans[i]);
return Ot(),0;
}
以及一些例題
BZOJ4543[POI2014]Hotel加強版
[cogs2652]秘術「天文密葬法」
P4292 [WC2010]重建計劃
參考文章
https://blog.bill.moe/long-chain-subdivision-notes/
http://www.cnblogs.com/zhoushuyu/p/9468669.html
長鏈剖分學習筆記