BZOJ 4530: [Bjoi2014]大融合(LCT維護子樹大小)

題目描述

權限傳送門

題目大意:一個圖,動態加邊。保證不會出現環,問你一條邊被多少條路徑經過。點數和詢問數不超過10^5.


思路

LCT維護子樹大小。

每個LCT上的點,維護其全子樹大小sum_siz,虛子樹(虛邊連出去的子樹)大小siz

一個點本身的大小算進siz裏,在Splay過程中維護sum_siz,即sum_siz=son[0]->sum_siz+son[1]->sum_siz+siz。對於全子樹大小的維護場合跟一般的Up是一樣的。

對於siz的維護,在改變邊的虛實和建立虛邊時維護,就是在Access和Link時維護。當Access砍掉右兒子的時候siz+=son[1]->sum_siz,接上x時siz-=x->sum_siz

Link的時候要特別注意,如果直接Evert(x),修改y的siz是不行的。因爲如果y不是根的話,y在原樹裏的祖先的sum_siz也是要維護的。這樣不方便,我們不妨將y也Evert到它所在的原樹的根,再令y->siz+=x->sum_siz,就沒有問題了。

維護子樹的操作大概就是這樣,根據我個人的幼稚理解,我們要拿一個點的子樹信息,只有當其處於原樹的根(Evert後)再Splay到根或其所在的Splay所維護的鏈上的最深處(Access後)才能得到正確的答案。

因爲我們維護sum_siz是在Splay上維護的,我們只能拿到其Splay上左右子樹的全子樹及虛子樹信息,只有當前點爲原樹的根且爲Splay的根時才能保證其全子樹信息與原樹子樹信息一致。同理,當一個點爲一條從根往下的鏈的末端時,它的虛子樹信息就是原子樹信息。(然而這題將siz寫成sum_siz交了居然A了??難道是我理解錯了??)

總之,在拿全子樹信息(或實邊信息)時要Splay,其餘情況不用。

胡扯了那麼久,其實題解就一句話。容易發現,一條邊的答案就是邊兩側的點數的乘積。這個用子樹大小就能算出來。

類似的,用這種方法可以維護子樹可加減的其他信息。(比如異或和之類的)


代碼

#include <bits/stdc++.h>
#define maxn 100010
using namespace std;

int n, q, cnt;

char s[5];

struct Tnode{
    Tnode *son[2], *fa;
    int parent, siz, sum_siz;
    bool rev;
    int Get_d(){return fa->son[1] == this;}
    void Connect(Tnode *now, int d){(son[d] = now)->fa = this;}
    void Up(){
        sum_siz = siz;
        if(son[0])  sum_siz += son[0]->sum_siz;
        if(son[1])  sum_siz += son[1]->sum_siz;
    }   
    void Down(){
        if(rev){
            swap(son[0], son[1]);
            if(son[0])  son[0]->rev ^= 1;
            if(son[1])  son[1]->rev ^= 1;
            rev = false;
        }
    }
}tree[maxn], *Tr[maxn];

Tnode *NewTnode(){
    tree[cnt].son[0] = tree[cnt].son[1] = tree[cnt].fa = NULL;
    tree[cnt].siz = tree[cnt].sum_siz = 1;
    tree[cnt].parent = 0;
    tree[cnt].rev = false;
    return tree+cnt++;
}

void Zig(Tnode *now){
    Tnode *last = now->fa;
    int d = now->Get_d();
    if(now->son[!d])  last->Connect(now->son[!d], d);
    else  last->son[d] = NULL;
    if(last->fa)  last->fa->Connect(now, last->Get_d());
    else  now->fa = NULL;
    now->Connect(last, !d);
    now->parent = last->parent;
    last->parent = 0;
    last->Up();
}

void Splay(Tnode *now){
    Tnode *last;
    while(now->fa){
        last = now->fa;
        if(last->fa)  last->fa->Down();
        last->Down();  now->Down();
        if(last->fa)  (now->Get_d() ^ last->Get_d()) ? Zig(now) : Zig(last);
        Zig(now);
    }
    if(!now->fa)  now->Down();
    now->Up();
}

void Change(int x){
    Splay(Tr[x]);
    if(Tr[x]->son[1]){
        Tr[x]->siz += Tr[x]->son[1]->sum_siz;
        Tr[x]->son[1]->fa = NULL;
        Tr[x]->son[1]->parent = x;
        Tr[x]->son[1] = NULL;
        Tr[x]->Up();
    }
}

void Access(int x){
    Change(x);
    int y = Tr[x]->parent;

    for(; y; x = y, y = Tr[x]->parent){
        Change(y);
        Tr[y]->Connect(Tr[x], 1);
        Tr[y]->siz -= Tr[x]->sum_siz;
        Tr[y]->Up();
        Tr[x]->parent = 0;
    }
}

void Evert(int x){
    Access(x);
    Splay(Tr[x]);
    Tr[x]->rev ^= 1;
}

void Link(int x, int y){
    Evert(x);
    Evert(y);
    Tr[x]->parent = y;
    Tr[y]->siz += Tr[x]->sum_siz;
}

long long Query(int x, int y){
    Evert(x);
    Access(y);
    Splay(Tr[x]);
    return 1LL * Tr[y]->siz * (Tr[x]->sum_siz - Tr[y]->siz);
}

int main(){

    scanf("%d%d", &n, &q);

    for(int i = 1; i <= n; i++)  Tr[i] = NewTnode();

    int x, y;
    while(q --){
        scanf("%s%d%d", s, &x, &y);
        if(s[0] == 'A')  Link(x, y);
        else  printf("%lld\n", Query(x, y));
    }

    return 0;
}

猶如晝去夜來

發佈了177 篇原創文章 · 獲贊 110 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章