[BZOJ4530]-大融合-LCT维护子树信息

说在前面

好久没更新博客了=w=
多校集训忙的要死,也累得要死….
要学的东西好多好多好多…
大家都很强啊,要好好的努力一把了!


题目

BZOJ4530传送门

题面

小强要在N个孤立的星球上建立起一套通信系统。这套通信系统就是连接N个点的一个树。这个树的边是一条一条添加上去的。在某个时刻,一条边的负载就是它所在的当前能够联通的树上路过它的简单路径的数量。
这里写图片描述
例如,在上图中,现在一共有了5条边。其中,(3,8)这条边的负载是6,因为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8)。现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的询问。

输入输出格式

输入格式:
第一行包含两个整数N,Q,表示星球的数量和操作的数量。星球从1开始编号。接下来的Q行,每行是如下两种格式之一:
A x y 表示在x和y之间连一条边。保证之前x和y是不联通的。
Q x y 表示询问(x,y)这条边上的负载。保证x和y之间有一条边。
数据范围: 1N,Q100000

输出格式:
对每个查询操作,输出一行一个整数,表示被查询的边的负载。


解法

一道很入门的LCT维护子树和=w=
每个Splay节点维护两个信息,LCT(splay)树上实儿子的子树大小(包括实儿子的虚儿子)rsiz,和虚儿子的子树大小isiz。
实儿子,就是 nd>fa>ch[0/1]==nd
虚儿子,就是 nd>fa>ch[0/1] != nd
每个Splay根节点的rsiz+isiz,表示的就是当前链顶在原树里的子树大小

对于题目中的操作:
连接操作就是Link操作,这时注意更新 虚子树大小
询问操作,先把其中一个点x换根,然后Access另外一个点y,那么y点的(虚子树大小+1)就是原树中y的子树大小。而x的子树大小,只需用总siz减去y的那部分即可


下面是自带大常数的代码

/**************************************************************
    Problem: 4530
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:1312 ms
    Memory:3556 kb
****************************************************************/
 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
 
int N , Q ;
struct Node{
    int rsiz , isiz ;
    bool rev ;
    Node *ch[2] , *fa ;
    void rever(){
        swap( ch[0] , ch[1] ) ;
        rev ^= 1 ;
    }
    void pushdown(){
        if( rev ){
            if( ch[0] ) ch[0]->rever() ;
            if( ch[1] ) ch[1]->rever() ;
            rev = false ;
        }
    }
    void update(){
        rsiz = 1 ;
        if( ch[0] ) rsiz += ch[0]->rsiz + ch[0]->isiz ;
        if( ch[1] ) rsiz += ch[1]->rsiz + ch[1]->isiz ;
    }
}w[100005] ;
 
bool isroot( Node *x ){
    if( !x->fa ) return true ;
    if( x->fa->ch[0] != x && x->fa->ch[1] != x ) return true ;
    return false ;
}
 
void Rotate( Node *nd , int aim ){
    Node *x = nd->ch[aim] ;
    if( nd->fa ){
        if( nd->fa->ch[0] == nd ) nd->fa->ch[0] = x ;
        if( nd->fa->ch[1] == nd ) nd->fa->ch[1] = x ;
    } x->fa = nd->fa ;
 
    if( x->ch[aim^1] ){
        x->ch[aim^1]->fa = nd ;
    } nd->ch[aim] = x->ch[aim^1] ;
 
    nd->fa = x , x->ch[aim^1] = nd ;
    nd->update() ;
    x->update() ;
}
 
Node *sta[100005] ;
int topp ;
 
void Splay( Node *nd ){
    Node *tmp = nd ; topp = 0 ;
    for( ; !isroot( tmp ) ; tmp = tmp->fa ) sta[++topp] = tmp ;
    sta[++topp] = tmp ;
    for( ; topp ; topp -- ) sta[topp]->pushdown() ;
 
    while( !isroot( nd ) ){
        Node *fa = nd->fa , *gdfa = fa->fa ;
        int pn = ( fa->ch[1] == nd ) , pf ;
        if( gdfa && !isroot( fa ) ){
            pf = ( gdfa->ch[1] == fa ) ;
            if( pf == pn ){
                Rotate( gdfa , pf ) ;
                Rotate( fa , pn ) ;
            } else {
                Rotate( fa , pn ) ;
                Rotate( gdfa , pf ) ;
            }
        } else Rotate( fa , pn ) ;
    }
}
 
void Access( Node *nd ){
    Node *tmp = NULL ;
    while( nd ){
        Splay( nd ) ;
        if( tmp )
            nd->isiz -= tmp->isiz + tmp->rsiz ;
        if( nd->ch[1] )
            nd->isiz += nd->ch[1]->isiz + nd->ch[1]->rsiz ;
        nd->ch[1] = tmp ;
        nd->update() ;
        tmp = nd ;
        nd = nd->fa ;
    }
}
 
void Makeroot( Node *x ){
    Access( x ) ; Splay( x ) ;
    x->rever() ;
}
 
void Link( Node *x , Node *y ){
    Makeroot( x ) ;
    Access( y ) ; Splay( y ) ;
    x->fa = y ;
    y->isiz += x->rsiz + x->isiz ;
}
 
void solve(){
    char opt[5] ;
    for( int i = 1 , x , y ; i <= Q ; i ++ ){
        scanf( "%s%d%d" , opt , &x , &y ) ;
        if( opt[0] == 'A' ){
            Link( w + x , w + y ) ;
        } else {
            Makeroot( w + x ) ;
            Access( w + y ) ;
            Splay( w + x ) ;
            printf( "%lld\n" , 1LL * ( w[y].isiz + 1 ) * ( w[x].rsiz + w[x].isiz - w[y].isiz - 1 ) ) ;
        }
    }
}
 
int main(){
    scanf( "%d%d" , &N , &Q ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        Node *nd = ( w + i ) ;
        nd->ch[0] = nd->ch[1] = nd->fa = NULL ;
        nd->rsiz = 1 , nd->isiz = 0 ;
        nd->rev = false ;
    }
    solve() ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值