[luogu-P4299] 首都(并查集 + LCT动态维护树的重心 / 维护虚儿子信息)

本文详细介绍了如何使用线段树的变形——链接切割树(LCT)来解决动态连通性问题。在LCT的基础上,通过合并、查询、查找和更新等操作,实现了快速判断两点是否在同一连通块中,并能在合并国家时保持树的平衡。代码中展示了LCT的具体实现,包括旋转、访问、分割和链接等操作,以及如何维护节点信息。
摘要由CSDN通过智能技术生成

problem

luogu-P4299

solution

本题考察了很经典的模型,运用了很经典的解法。

本题用到了重心的两个性质:

  • 两棵树合并为同一棵树时,新的重心一定在原来两棵树各自重心的路径上。

  • 重心为根时的最大子树大小最小,不超过 s i z / 2 siz/2 siz/2

    如果树节点个数为奇数,则只有一个重心。否则有两个重心。

找到 x , y x,y x,y 各自所在的国家的重心 g x , g y gx,gy gx,gy

x , y x,y x,y 连边完成国家合并。

然后把 g x − g y gx-gy gxgy 的路径 split \text{split} split 出来。

接着就暴力 dfs \text{dfs} dfs 根据左右儿子的 siz \text{siz} siz 判断走左还是走右,因为每次 s i z siz siz 至少减小一半,所以时间复杂度仍然是一个 l o g log log

具体而言:当左右子树 siz \text{siz} siz 都不超过总个数的一半时,将这个点纳入重心备选。然后看左右儿子谁的 s i z siz siz 更大,就往哪边走。

但是很遗憾 LCT \text{LCT} LCT 认父不认子,对于一个点只能记录两个左右儿子的 siz \text{siz} siz

我们好像不能获得一个点的子树内所有的节点个数?

这就是一个 LCT \text{LCT} LCT 经典应用了。

t [ i ] . s i z t[i].siz t[i].siz 记录 i i i 子树大小, t [ i ] . t o t t[i].tot t[i].tot 记录除开两个左右儿子的 s i z siz siz,其余虚儿子的 s i z siz siz 的和。

更新就需要写成:

t[x].siz = t[t[x].son[0]].siz + t[t[x].son[1]].siz + 1 + t[x].tot;

与此同时,所有涉及更改虚实儿子的,都要重新维护 t o t tot tot

一般就是 access \text{access} access 中的操作,会多加一行。

t[x].tot += t[t[x].son[1]].siz - t[son].siz;

而且为了保证信息的时效性,我们的 link \text{link} link 操作要还是像以前一样,直接记一下 x x x 的父亲是 y y y 就不行了。

也不能直接把 y y y 的信息更新一下就完了。

因为一旦 x , y x,y x,y 连边,各自所在的子树的位置一路往上信息都要修改。

所以我们不如直接让 x , y x,y x,y 成为联通块的根, split(x,y) \text{split(x,y)} split(x,y) 即可。

由于 L C T LCT LCT findroot \text{findroot} findroot 慢得慌,我们直接用并查集维护每个联通块的重心即可,重心的异或和也用个变量维护即可。

具体可见代码实现。

code

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

namespace LCT {
    int top = 0, sta[maxn];
    struct node { int son[2], fa, tot, tag, siz; }t[maxn];
    bool root( int x ) { return t[t[x].fa].son[0] ^ x and t[t[x].fa].son[1] ^ x; }
    void pushup( int x ) { t[x].siz = t[t[x].son[0]].siz + t[t[x].son[1]].siz + 1 + t[x].tot; }
    void reverse( int x ) { swap( t[x].son[0], t[x].son[1] ); t[x].tag ^= 1; }
    void pushdown( int x ) {
        if( ! t[x].tag ) return;
        if( t[x].son[0] ) reverse( t[x].son[0] );
        if( t[x].son[1] ) reverse( t[x].son[1] );
        t[x].tag = 0;
    }
    void rotate( int x ) {
        int fa = t[x].fa;
        int Gfa = t[fa].fa;
        int d = t[fa].son[1] == x;
        if( ! root( fa ) ) t[Gfa].son[t[Gfa].son[1] == fa] = x;
        t[x].fa = Gfa;
        if( t[x].son[d ^ 1] ) t[t[x].son[d ^ 1]].fa = fa;
        t[fa].son[d] = t[x].son[d ^ 1];
        t[x].son[d ^ 1] = fa;
        t[fa].fa = x;
        pushup( fa );
        pushup( x );
    }
    void splay( int x ) {
        sta[++ top] = x; int y = x;
        while( ! root( y ) ) sta[++ top] = y = t[y].fa;
        while( top ) pushdown( sta[top --] );
        while( ! root( x ) ) {
            int fa = t[x].fa, Gfa = t[fa].fa;
            if( ! root( fa ) ) (t[Gfa].son[1] == fa) ^ (t[fa].son[1] == x) ? rotate( x ) : rotate( fa );
            rotate( x ); 
        }
    }
    void access( int x ) { 
        for( int son = 0;x;son = x, x = t[x].fa ) { 
            splay( x );
            t[x].tot += t[t[x].son[1]].siz - t[son].siz;
            t[x].son[1] = son;
            pushup( x ); 
        }
    }
    void makeroot( int x ) { access( x ); splay( x ); reverse( x ); }
    void split( int x, int y ) { makeroot( x ); access( y ); splay( y ); }
    void link( int x, int y ) { split( x, y ); t[t[x].fa = y].tot += t[x].siz; pushup( y ); }
    int dfs( int x ) {
        int lsum = 0, rsum = 0, siz = t[x].siz >> 1, flag = t[x].siz & 1, ans = 1e9;
        while( x ) {
            pushdown( x );
            int lcnt = lsum + t[t[x].son[0]].siz;
            int rcnt = rsum + t[t[x].son[1]].siz;
            if( lcnt <= siz and rcnt <= siz ) {
                if( flag ) { ans = x; break; }
                else ans = min( ans, x );
            }
            if( lcnt < rcnt ) { 
                lsum += t[x].siz - t[t[x].son[1]].siz; // + t[t[x].son[0]].siz + t[x].tot + 1
                x = t[x].son[1];
            }
            else {
                rsum += t[x].siz - t[t[x].son[0]].siz; // +t[t[x].son[1]].siz + t[x].tot + 1
                x = t[x].son[0];
            }
        }
        splay( ans );
        return ans;
    }
}

int f[maxn];
int find( int x ) { return x == f[x] ? x : f[x] = find( f[x] ); }

int main() {
    int ans = 0, x, y, n, m; char opt[10];
    scanf( "%d %d", &n, &m );
    iota( f + 1, f + n + 1, 1 );
    for( int i = 1;i <= n;i ++ ) ans ^= i;
    for( int i = 1;i <= m;i ++ ) {
        scanf( "%s", opt );
        switch( opt[0] ) {
            case 'X' : printf( "%d\n", ans ); break;
            case 'Q' : {
                scanf( "%d", &x );
                printf( "%d\n", find( x ) );
                break;
            }
            case 'A' : {
                scanf( "%d %d", &x, &y );
                LCT :: link( x, y );
                x = find( x ), y = find( y );
                LCT :: split( x, y );
                int g = LCT :: dfs( y );
                ans ^= x ^ y ^ g;
                f[x] = f[y] = f[g] = g;
                break;
            }
        }
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值