1. 程式人生 > >【bzoj2759】一個動態樹好題

【bzoj2759】一個動態樹好題

portal endif 套路 open 就是 chan 查詢 return val

Portal -->bzoj2759

Solution

  哇我感覺這題真的qwq是很好的一題呀qwq

  很神qwq反正我真的是自己想怎麽想都想不到就是了qwq

  

  首先先考慮一下簡化版的問題應該怎麽解決:

  1、如果說我知道\(x_1\equiv k_1*x_2+b_1(mod\ 10007)\),並且\(x_2\)已知,那麽顯然有當\(k_1=0\)時有\(x_1=x_2\)\(k_1=1\)\(b_1=0\)時有無數組解,\(k_1=1\)\(b_1\)不為\(0\)時無解;\(k_1>1\)時逆元求解(因為\(10007\)是質數嘛)

  2、如果說我知道\(x_1\equiv k_1*x_2+b_1(mod\ 10007)\)

\(x_2\equiv k_2*x_1+b_1(mod \ 10007)\),那麽怎麽求\(x_1\)

  有一種很粗暴的方法就是直接把第一條式子中的\(x_2\)用第二條式子的右邊部分替換掉,然後直接解

?  

  然後我們可以想辦法往這個方向靠

  我們來小小的轉化一下這個問題,我們考慮把每一條式子\(x_i=k_i*x_{p_i}+b_i\),轉化為由\(x_{p_i}\)\(x_i\)連一條有向邊,然後這樣的話我們就可以得到一個。。基環外向樹森林,大概是若幹個長這個樣子的東西(額當然這裏沒有把邊的方向畫出來):技術分享圖片

  處理這樣的東西,有一個比較套路的方法就是拆掉環上一條邊然後變成樹來維護處理

  我們選環上的其中一個點作為這塊東西的\(rt\),然後將拆掉的那條邊(某個點\(y\)指到\(rt\))對應的點\(y\)記為\(sp[rt]\),也就是\(rt\)的一個\(special\ father\)

  然後我們考慮用LCT來維護這個東西,但是具體維護什麽呢

  

  這裏有一個很神的想法,對於每一個splay上的節點,我們維護其子樹內最左邊的節點\(L\)(也就是深度最淺的那個)的\(sp\)表達最右邊的那個節點\(R\)(深度最深的那個)的表達式的兩個系數,也就是:
\[ x_{R}=k*x_L+b \]
  對於splay上的每個節點我們維護上面這個式子裏面的\(k\)

\(b\)分別是多少(記作\(info[x].k\)\(info[x].b\)

  這樣一來,我們對於一個節點\(x_i\)做了\(access(x_i)\)以及\(splay(x_i)\)之後,\(info[x]\)中存的表達式其實就是:
\[ x_i=k*sp[rt]+b \]
  那麽對於每次查詢(記查詢的那個點為\(x_a\)),我們需要做的就是用上面的操作得到\(x_i\)\(sp[rt]\)之間的表達式,然後只要再得到\(sp[rt]\)關於自己的表達式我們就可以求出\(sp[rt]\)進而求得\(x_i\)了。後者的話因為根據定義\(sp[rt]\)應該是這棵樹中的某個節點,所以我們直接用同樣的\(access+splay\)操作就可以得到\(sp[rt]\)關於自己的表達式了

  然後對於修改的話,我們需要分類討論一下(可以自己畫個圖理解一下就很清晰了)

1、\(rt=x\)

?  這裏又可以再分兩類

?  如果說\(p\)在這棵樹中,那麽修改\(sp[rt]\)即可;否則\(sp[rt]=0\)然後將\(rt\)接到\(p\)這個節點上面去,作為\(p\)的一個兒子

2、\(rt!=x\)

  不管別的首先我們都要將\(x\)和原來的\(fa[x]\)斷開

?  如果說\(x\)\(rt\)\(sp[rt]\)的這個環上的話,斷開之後會有一個影響,就是\(sp[rt]\)指向\(rt\)這條邊不需要斷開了,所以我們要將\(rt\)連到\(sp[rt]\)那裏去作為其一個兒子

  然後不管是\(x\)是否在環上,我們都要判斷如果說\(p\)在這棵樹上,那麽\(sp[x]=p\),否則\(sp[x]=0\)然後將\(x\)連到\(p\)那裏去作為其一個兒子

  

?  這些都討論完了之後,我們來想想這個關鍵的\(info[x]\)要怎麽維護

  註意到這個在\(update\)的時候是必須按照一定順序的,因為一個節點\(x\)的信息只能和原樹中的\(fa[x]\)合並

  具體一點就是:
\[ \begin{aligned} x&=k_1*x_{fa}+b_1\x_{fa}&=k_2*x'+b_2\\\downarrow\\x&=k_1(k_2*x'+b_2)+b_1 \end{aligned} \]
  我們用\(ch[x][0]\)\(ch[x][1]\)表示splay中\(x\)節點的左右兒子

?  假設我們現在知道\(info[ch[x][0]]]\)\(info[ch[x][1]]\),我們想要得到\(info[x]\),那麽其實只要先將\(info[ch[x][0]]\)表示的式子和\(x\)本身的式子先合並存為\(info[x]\),再將\(info[x]\)\(info[ch[x][1]]\)合並即可,具體的話就是因為左子樹中深度最深的節點在原樹上就是\(fa[x]\),同理右子樹中深度最淺的節點在原樹上的\(fa\)就是\(x\),所以直接這麽合並就好了

  

  想明白了的話還是挺好寫的ovo(廢話qwq然而我想了一天。。。)

?  

  代碼大概長這個樣子

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=30010,MOD=10007,LCT=N;
struct Data{
    int k,b;
    Data(){}
    Data(int k1,int b1){k=k1; b=b1;}
    friend Data operator + (Data x,Data y)
    {return Data(x.k*y.k%MOD,(x.b*y.k%MOD+y.b)%MOD);}
}val[N];
int h[N],vis[N],Fa[N],inv[N];
int n,m,tot,Cnt;
namespace Lct{/*{{{*/
    int ch[LCT][10],fa[LCT],sp[LCT];
    Data info[LCT];
    int tot;
    bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    int which(int x){return ch[fa[x]][1]==x;}
    void update(int x){//order is important
        info[x]=val[x];
        if (ch[x][0]) info[x]=info[ch[x][0]]+info[x];
        if (ch[x][1]) info[x]=info[x]+info[ch[x][1]];
    }
    void rotate(int x){
        int dir=which(x),f=fa[x];
        if (!isroot(f)) ch[fa[f]][which(f)]=x;
        fa[x]=fa[f]; fa[f]=x;
        if (ch[x][dir^1]) fa[ch[x][dir^1]]=f;
        ch[f][dir]=ch[x][dir^1];
        ch[x][dir^1]=f;
        update(f); update(x);
    }
    void splay(int x){
        for (int f=fa[x];!isroot(x);f=fa[x]){
            if (!isroot(f))
                rotate(which(f)==which(x)?f:x);
            rotate(x);
        }
    }
    void access(int x){
        for (int last=0;x;last=x,x=fa[x]){
            splay(x);
            ch[x][1]=last;
            update(x);
        }
    }
    int get_rt(int x){
        access(x); splay(x);
        while (ch[x][0]) x=ch[x][0];
        return x;
    }
    void query(int x){
        Data tmp1,tmp2;
        access(x); splay(x);
        tmp1=info[x];//sp[rt]-->x

        int rt=get_rt(x);
        access(sp[rt]); splay(sp[rt]);
        tmp2=info[sp[rt]];//sp[rt]-->sp[rt]
        
        if (tmp2.k==1){
            if (tmp2.b==0) printf("-2\n");
            else printf("-1\n");
        }
        else{
            int valrt=inv[(1-tmp2.k+MOD)%MOD]*tmp2.b%MOD;
            printf("%d\n",(tmp1.k*valrt%MOD+tmp1.b)%MOD);
        }
    }
    void Cut(int x){
        access(x); splay(x);
        ch[x][0]=fa[ch[x][0]]=0;
        update(x);
    }
    bool InCir(int x,int y){//x in cir(y,sp[y])?
        access(sp[y]);
        splay(sp[y]);
        splay(x);
        return x==sp[y]||(!isroot(sp[y]));
    }
    void change(int x,int k,int p,int b){
        access(x);
        splay(x);
        val[x]=Data(k,b);
        update(x);

        int rt=get_rt(x);
        if (rt==x){
            if (get_rt(p)==rt) sp[x]=p;
            else sp[rt]=0,fa[rt]=p;
        }
        else{
            if (InCir(x,rt)){
                Cut(x);
                splay(rt);
                fa[rt]=sp[rt];
                sp[rt]=0;
            }
            else
                Cut(x);
            if (get_rt(p)==x)
                sp[x]=p;
            else 
                sp[x]=0,fa[x]=p;
        }
    }
}/*}}}*/

void prework(int x){
    vis[x]=Cnt;
    Lct::fa[x]=Fa[x];
    if (vis[Fa[x]]==Cnt){
        Lct::fa[x]=0;
        Lct::sp[x]=Fa[x];
        return;
    }
    prework(Fa[x]);
}

void get_inv(int n){
    inv[1]=1;
    for (int i=2;i<=n;++i)
        inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
};

int main(){/*{{{*/
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
#endif
    char op[5];
    int x,p,k,b;
    scanf("%d",&n);
    for (int i=1;i<=n;++i)
        scanf("%d%d%d",&val[i].k,&Fa[i],&val[i].b);
    get_inv(N-10);
    Cnt=0;
    for (int i=1;i<=n;++i)
        if (!vis[i])
            ++Cnt,prework(i);

    scanf("%d",&m);
    for (int i=1;i<=m;++i){
        scanf("%s",op);
        if (op[0]=='A'){
            scanf("%d",&x);
            Lct::query(x);
        }
        else{
            scanf("%d%d%d%d%d",&x,&k,&p,&b);
            Lct::change(x,k,p,b);
        }
    }
}/*}}}*/

【bzoj2759】一個動態樹好題