1. 程式人生 > >並不對勁的LCT

並不對勁的LCT

cst 不用 圖片 put iomanip clu amp isp sdi

LCT,是連貓樹(link-cat-tree)的縮寫。它是樹鏈剖分和splay的結合版本。

由於有很多關於LCT的文章以及這並不是對勁的文章,並不對勁的人並不打算講得太詳細。

推薦:詳細的LCT->技術分享圖片

想必大家都知道splay+樹剖=LCT

splay雖然常數較大,但是它好寫好調(大部分操作都可以把左右邊界轉上去然後直接操作),而且還能維護序列。

樹剖是將一棵樹切分成很多條鏈,再將它們首尾相接拼成一個序列。可以用線段樹來維護序列,進行區間操作,不過並不能插入和刪除。

那麽用splay來維護區間是不是就可以插入和刪除了呢?想必是可以的。但是插入和刪除會導致子樹大小有變化,這樣輕重鏈剖分就不適用了。不過,和splay類似地,雖然聽上去很玄學,但是期望復雜度是可以被證明為log級別的;和splay類似地,並不對勁的人並不會證明。

講講幾個操作吧:

1.上/下傳標記,判斷是左/右兒子

void mark(int u){if(u)swap(ls,rs),re[u]^=1;}
void pu(int u){sum[0]=0,sum[u]=sum[ls]^sum[rs]^key[u],sum[0]=0;}
void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=0;}
int getso(int u){return son[fa[u]][0]==u?0:1;}

和splay沒什麽區別。pushup時可以在計算前後都強行令sum[0]=0。

2.判斷一個點是否為splay的根

int notrt(int
u){return son[fa[u]][0]==u||son[fa[u]][1]==u;}

splay直接判斷有沒有父親(我)。但是LCT是由很多棵對應一條重鏈的splay組成的。這樣【同一樹的不同splay】為了與【同一splay(父、子關系都有)】和【不同樹(父、子關系都沒有)】區分,是有【認父(我)不認子(你)】的規定。也就是說,splay的根可能有父親(我),但是它的父親(我)未必認它這個兒子(你)。

3.旋轉(rotate)

void rot(int u)
{
    int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^1
]; if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^1]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u; pu(rson),pu(fu),pu(u),pu(ffu); }

和splay區別不大。但是被旋轉點u的祖父可能和u不在同一棵splay上,要判斷旋轉點的父親是不是根。

4.伸展(splay)

void splay(int u)
{
    int v=u;top=0,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
    while(top)pd(st[top--]);
    while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
}

在普通的splay中,只有從根走到某個點才能將這個點伸展,這樣肯定經過了【根到某點的路徑】,那麽路徑上的所有標記應該已經被下傳了。但是LCT中,並不會從根走到那個點。也就是說,那個點上面可能有為下傳的標記。所有要先下傳【根到某點的路徑(是splay的根!!)】上的所有標記再進行下一步。

5.將【根到某點的路徑】變成重鏈(access)

void acs(int u){for(int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}

無論怎麽改重鏈,它們都會還在同一棵樹裏,所以不用改父親(我)。

6.求出某點所在樹的根(getroot)

int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}

splay維護的序列中的數的順序是先走重鏈的DFS序,所以根在DFS序中肯定比某點靠前。把根和該點放在同一個splay後,splay維護的序列的最前面的那個數就是根。

7.將某點變成根(chroot)

void chrt(int u){acs(u),splay(u),mark(u);}

將該點access後,會發現它恰巧是這條重鏈的splay對應的序列中最靠後的。而根是最靠前的。所以把序列翻轉就行了。

8.求某兩點之間路徑的和(getroad)

void getrd(int u,int v){chrt(u),acs(v),splay(v);}

讓一點為根,使兩點在同一條重鏈上。這棵splay就是想要求和的部分。

9.將兩點之間連邊(link)

void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}

將一個點轉到根後判斷另一個點的根是不是它。由於getroot是已經將另一個點轉到splay的根,直接按【認父不認子】的規則將在樹根的點接到在splay根的點上。

10.斷開兩點之間的邊(cat)

void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][0]=0;pu(v);}

還是將一個點轉到根。先判斷另一個點是不是和在以這個點為根的樹上(這時這個點已經在splay根上了)。之間有重鏈相連還要滿足它們在splay的序列中相鄰。兩條都滿足就可以斷了,要把父子關系都斷幹凈。splay根的兒子情況改變,所以需要pushup。

完整代碼如下:

技術分享圖片
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define maxn 300010
#define ls son[u][0]
#define rs son[u][1]
using namespace std;
inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(isdigit(ch)==0 && ch!=-)ch=getchar();
    if(ch==-)f=-1,ch=getchar();
    while(isdigit(ch))x=x*10+ch-0,ch=getchar();
    return x*f;
}
inline void write(int x)
{
    int f=0;char ch[20];
    if(!x){puts("0");return;}
    if(x<0){putchar(-);x=-x;}
    while(x)ch[++f]=x%10+0,x/=10;
    while(f)putchar(ch[f--]);
    putchar(\n);
}
struct LCT
{
int fa[maxn],son[maxn][3],sum[maxn],key[maxn],re[maxn],st[maxn],top;
void mark(int u){if(u)swap(ls,rs),re[u]^=1;}
void pu(int u){sum[0]=0,sum[u]=sum[ls]^sum[rs]^key[u],sum[0]=0;}
void pd(int u){if(re[u])mark(ls),mark(rs),re[u]=0;}
int getso(int u){return son[fa[u]][0]==u?0:1;}
int notrt(int u){return son[fa[u]][0]==u||son[fa[u]][1]==u;}
void rot(int u)
{
    int fu=fa[u],ffu=fa[fu],l=getso(u),fl=getso(fu),rson=son[u][l^1];
    if(notrt(fu))son[ffu][fl]=u;son[fu][l]=rson,son[u][l^1]=fu,fa[rson]=fu,fa[u]=ffu,fa[fu]=u;
    pu(rson),pu(fu),pu(u),pu(ffu);
}
void splay(int u)
{
    int v=u;top=0,st[++top]=v;while(notrt(v))st[++top]=v=fa[v];
    while(top)pd(st[top--]);
    while(notrt(u)){int fu=fa[u];if(notrt(fu))rot(getso(u)^getso(fu)?u:fu);rot(u);}
    pu(u);
}
void acs(int u){for(int v=0;u;v=u,u=fa[u])splay(u),rs=v,pu(u);}
int getrt(int u){acs(u),splay(u);while(u){pd(u);if(!ls)break;u=ls;}return u;}
void chrt(int u){acs(u),splay(u),mark(u);}
void getrd(int u,int v){chrt(u),acs(v),splay(v);}
void link(int u,int v){chrt(u);if(getrt(v)!=u)fa[u]=v;}
void cat(int u,int v){chrt(u);if(getrt(v)==u&&fa[u]==v&&!rs)fa[u]=son[v][0]=0;pu(v);}
}t;
int main()
{
    int n=read(),q=read(),f,x,y;
    for(int i=1;i<=n;i++)t.key[i]=read();
    while(q--)
    {
        f=read(),x=read(),y=read();
        if(f==0)t.getrd(x,y),write(t.sum[y]);
        if(f==1)t.link(x,y);
        if(f==2)t.cat(x,y);
        if(f==3)t.splay(x),t.key[x]=y;
    }
    return 0;
}
View Code

並不對勁的LCT