BZOJ 4530: [Bjoi2014]大融合(LCT维护子树大小)

8 篇文章 0 订阅

题目描述

权限传送门

题目大意:一个图,动态加边。保证不会出现环,问你一条边被多少条路径经过。点数和询问数不超过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;
}

犹如昼去夜来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值