1. 程式人生 > >長鏈剖分學習筆記

長鏈剖分學習筆記

data str 繼承 write class 一個 stdin init true

長鏈剖分

長鏈剖分用於優化一些特殊的dp,可以將某些\(O(n)\)的時間復雜度降為均攤\(O(1)\)

感覺這玩意兒大部分東西都和樹鏈剖分挺像,理解的時候可以照著輕重鏈剖分那種去理解

定義

長鏈

和重鏈差不多,就是從某一個節點走到它子樹中最深的節點所經過的路徑

重兒子

某個節點在長鏈上的兒子就是它的重兒子(很好奇為什麽不叫長兒子)。

頂點

某條長鏈中深度最小的點就是該長鏈的頂點

不難看出長鏈剖分和樹鏈剖分其實很像,樹鏈剖分中重兒子所在子樹具有最大的size,而長鏈剖分中重兒子所在子樹具有最大的dep

性質

1.所有鏈長總和為\(O(n)\)

2.任意一個節點\(x\)\(k\)級祖先\(y\)

所在長鏈長度一定大於等於\(k\)

正確性的話,自己yy一下好了

應用

因為這東西的基礎基本和樹剖沒什麽區別所以不多講了,還是從應用裏還說明它的一下特殊性好了

O(1)在線查詢某一個點的\(k\)級祖先

例題lxhgww的奇思妙想

我們設\(len[u]\)\(u\)所在長鏈的長度,對於每一個長鏈的頂點,我們維護它的1到\(len[u]\)級兒子以及1到\(len[u]\)級祖先

同時預處理找祖先的倍增數組,以及\(1\)\(n\)的每一個數字的二進制最高位即\(highbit\)

那麽對於每一個詢問\((u,k)\),我們設\(r=highbit(k)\),那麽我們用預處理的倍增數組讓\(u\)

跳到它的\(r\)級祖先\(v\)

因為\(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

長鏈剖分學習筆記