[線段樹系列] LCT打延遲標記的正確姿式

這一篇博客將教你什麼?html

如何用LCT打延遲標記,LCT和線段樹延遲標記間的關係,爲何延遲標記要這樣打。c++

——正片開始——算法

學習這一篇博客前,確保你會如下知識:
數據結構

Link-Cut-Tree,普通線段樹函數

固然,不會也沒有關係,你能夠先收藏這篇博客,等你學了之後再來看。學習

 

最好經過了這一道題:【模板】線段樹Ⅱspa

沒有經過也不要緊,對於本篇的知識只是一個啓發做用。code

咱們平時使用的Link-Cut-Tree通常只須要打一個翻轉標記rev[x]。htm

而後咱們用pushR(x)函數來下發翻轉標記。blog

那麼咱們如今來看這樣一道題:TreeⅡ

練習LCT打標記的絕世好題,能夠說就是一道模板題了。

咱們來看它須要維護什麼:樹的權值。

若是能把這個操做2去掉,樹剖將絕殺,惋惜換不得。

單走一個「樹」,nice,直接LCT。

詢問權值和,能夠,給LCT來一個下傳標記,開始你的操做秀。

咱們直接來看pushdown部分:

操做有乘和加兩種,根據運算法則,乘法優先,因此首先判斷

if(lazM[x]!=1){...}

而後是加法

if(lazA[x]){...}

最後回到咱們的翻轉標記。

而後咱們來看pushM和pushA部分

#define mul(x,y) x*=y;x%=MOD;
inline void pushM(unsigned int x,unsigned int d){
    mul(sum[x],d);mul(val[x],d);//節點信息直接更新
    mul(lazM[x],d);mul(lazA[x],d);//按照運算法則先把乘標記乘了,再把加標記乘了
}

有沒有回想起什麼?沒錯,就是線段樹的懶標記。

咱們看看線段樹2的懶標記下傳部分

void pushdown(int p){
        mul(p<<1)=(mul(p<<1)*mul(p))%P;
        mul(p<<1|1)=(mul(p<<1|1)*mul(p))%P;
        add(p<<1)=(add(p<<1)*mul(p))%P;
        add(p<<1|1)=(add(p<<1|1)*mul(p))%P;
        sum(p<<1)=(sum(p<<1)*mul(p))%P;
        sum(p<<1|1)=(sum(p<<1|1)*mul(p))%P;//按照運算法則更新標記和節點信息
        mul(p)=1;
        add(p<<1)=(add(p<<1)+add(p))%P;
        add(p<<1|1)=(add(p<<1|1)+add(p))%P;
        sum(p<<1)=(sum(p<<1)+add(p)*(r(p<<1)-l(p<<1)+1))%P;
        sum(p<<1|1)=(sum(p<<1|1)+add(p)*(r(p<<1|1)-l(p<<1|1)+1))%P;//按照運算法則更新標記和節點信息
        add(p)=0;
}

驚人的一致。

猜到pushA的寫法了吧?那我不講了,後面有代碼。

透過現象看本質。

兩種數據結構都是在維護一個區間,只不過LCT維護的樹上一段路徑的區間。

若是這道題把操做2去掉,咱們用樹鏈剖分寫,線段樹維護,同樣是這樣打標記。

爲何?

若是你當初學線段樹的時候理解了線段樹2打標記裏面先成後加的原理,你可能思考一下就明白了。

這裏的緣由和線段樹很是類似:精度。

咱們的標記是打在父節點上的,告訴它它的孩子加了多少,乘了多少。

若是咱們先加了那麼多,再乘那麼多,結果是不同的,若是非要等價,須要對式子變形。

咱們看這樣一個式子:(a+b)*c,它並不等價於a*c+b,運算的順序是會影響結果的。

然而咱們打標記的時候並不能肯定順序。這時咱們爲什麼不用上很早就明白的運算法則呢?

看下面兩種順序:

先+後*:(a+b)*c = a*c+b*c,先*後+:(a+b)*c = a*c+b

咱們發現,先+後*的式子並不等於先*後+的式子,要讓它相等必須在加的那一項也*上c。

可是要讓先*後+的式子轉化成先+後*的式子,咱們就必須用到除法,就會變成實數運算,還有可能獲得無限小數影響精度。因此咱們只須要使用先*後+的優先順序,而且在打乘法標記時把加法標記也乘上這個值就能夠了。

分析完後,相信各位應該能理解數據結構「懶標記」的概念以及爲什麼選擇這種優先順序了。

那麼剩下的就是很正常的LCT操做了,給出此題的代碼。

注意這道題有一個坑點:模數是51061,看上去很小,然而暗藏出題人的心機。

咱們來看51061的平方 ——> 2516125921,再看int的數據範圍 —— > 2147483647

因而咱們須要開long long,可是我寫的時候爲了卡經常使用了unsigned int。

#include<bits/stdc++.h>
using namespace std;
int n,q;
const int N=1000010;
#define MOD 51061
unsigned int fa[N],val[N],ch[N][2],rev[N],lazM[N],lazA[N],siz[N],sum[N],stk[N];
#define add(x,y) x+=y;x%=MOD;
#define mul(x,y) x*=y;x%=MOD;
inline unsigned int read(){
    unsigned int data=0,w=1;char ch=0;
    while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
    return data*w;
}
inline bool chk(unsigned int x){
    return ch[fa[x]][1]==x;
}
inline bool nroot(unsigned int x){
    return ch[fa[x]][0]==x||ch[fa[x]][1]==x;
}
inline void pushup(unsigned int x){
    sum[x]=(sum[ch[x][0]]+sum[ch[x][1]]+val[x])%MOD;
    siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
}
template<class T>inline void fswap(T&x,T&y){
    T t=x;x=y;y=t;
}
inline void pushR(unsigned int x){
    fswap(ch[x][0],ch[x][1]);
    rev[x]^=1;
}
inline void pushM(unsigned int x,unsigned int d){
    mul(sum[x],d);mul(val[x],d);
    mul(lazM[x],d);mul(lazA[x],d);
}
inline void pushA(unsigned int x,unsigned int d){
    add(sum[x],d*siz[x]);add(val[x],d);
    add(lazA[x],d);
}
inline void pushdown(unsigned int x){
    if(lazM[x]!=1){
        pushM(ch[x][0],lazM[x]);
        pushM(ch[x][1],lazM[x]);
        lazM[x]=1;
    }
    if(lazA[x]){
        pushA(ch[x][0],lazA[x]);
        pushA(ch[x][1],lazA[x]);
        lazA[x]=0;
    }
    if(rev[x]){
        if(ch[x][0])pushR(ch[x][0]);
        if(ch[x][1])pushR(ch[x][1]);
        rev[x]=0;
    }
}
inline void rotate(unsigned int x){
    int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
    ch[y][k]=w;if(w)fa[w]=y;
    if(nroot(y))ch[z][chk(y)]=x;fa[x]=z;
    ch[x][k^1]=y;fa[y]=x;
    pushup(y);pushup(x);
}
inline void splay(unsigned int x){
    int y=x,z=0;
    stk[++z]=y;
    while(nroot(y))stk[++z]=y=fa[y];
    while(z)pushdown(stk[z--]);
    while(nroot(x)){
        y=fa[x];z=fa[y];
        if(nroot(y)){
            if(chk(x)==chk(y))rotate(y);
            else rotate(x);
        }rotate(x);
    }
    pushup(x);
}
inline void access(unsigned int x){
    for(int y=0;x;x=fa[y=x])
        splay(x),ch[x][1]=y,pushup(x);
}
inline void makeroot(unsigned int x){
    access(x);splay(x);
    pushR(x);
}
inline int findroot(unsigned int x){
    access(x);splay(x);
    while(ch[x][0])pushdown(x),x=ch[x][0];
    splay(x);
    return x;
}
inline void split(unsigned int x,unsigned int y){
    makeroot(x);
    access(y);splay(y);
}
inline void link(unsigned int x,unsigned int y){
    makeroot(x);
    if(findroot(y)!=x)fa[x]=y;
}
inline void cut(unsigned int x,unsigned int y){
    makeroot(x);
    if(findroot(y)==x && fa[y]==x && !ch[y][0]){
        fa[y]=ch[x][1]=0;
        pushup(x);
    }
}
int main(){
    n=read();q=read();
    for(int i=1;i<=n;i++)val[i]=siz[i]=lazM[i]=1;
    for(int i=1;i<n;i++){
        int a=read();int b=read();
        link(a,b);
    }
    char opt[10];
    int u,v,d;
    while(q--){
        scanf("%s",opt);
        if(opt[0]=='+'){
            u=read();v=read();d=read();
            split(u,v);pushA(v,d);
        }else if(opt[0]=='-'){
            u=read();v=read();
            cut(u,v);
            u=read();v=read();
            link(u,v);
        }else if(opt[0]=='*'){
            u=read();v=read();d=read();
            split(u,v);pushM(v,d);
        }else{
            u=read();v=read();
            split(u,v);
            printf("%d\n",sum[v]);
        }
    }
    return 0;
}

其實這也揭示了數據結構間的聯繫:形式不一樣,做用類似。

透過現象看本質,經過結果推緣由,都是學習數據結構的重要方式。

數據結構不僅是背背代碼,用來加速這麼簡單的,若是明白了數據結構的運行方式和原理,

你也必定會感慨裏面蘊含着的發明者的智慧和它給你帶來的知識上的進步。

我是燈塔...一個喜歡數據結構的OIer博主,關注我,我將給你帶來更多精彩的文章。

相關文章
相關標籤/搜索