HDU 5967 小R与手机 CDQ分治+回滚并查集

该博客介绍了如何利用CDQ分治和回滚并查集解决HDU 5967题目中的问题。内容涉及处理非树边的策略,即当非树边未来可能仍作为非树边时,如何通过删除环上最小删除时间的边来避免复杂性,确保边的更新不会影响已建立的树结构。通过这种方法,可以简化问题,使用LCT(Link-Cut Tree)数据结构有效地维护图的状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

快退役了还写博客,我真是棒棒呢 >_<
这道题网上通用的解法是LCT,虽然LCT的确可以做,不过我还是一直惦记这宦壕从合肥回来以后嚷嚷着这题我是用可持久化并查集+CDQ分治过的
(当然了,他肯定是在吹牛,我怀疑他根本不知道可持久化并查集是什么。。。可回滚并查集和可持久化并查集还是有些区别的
可持久化并查集要用可持久化数组(即可持久化线段树实现),可以回退任意历史版本,但是实际上没什么用,可回滚并查集在cdq分治、莫队中都有很广泛的应用,
他使用按秩合并的方式,保留所有修改,每次将所有修改撤销来实现,实际上就是对某一时刻做副本只能回滚到那一时刻)


我百思不得其解,当然是我比较蠢,以前没见过这类图分治的题
直到前几天要退役了,CCPC-FINAL打了个铜,觉得自己什么都不会,还是学一波吧
这类问题有个非常经典的问题,bzoj 4025 
这种动态增删边问题,可以把每条边出现时间都扣出来形成一个区间[st,ed],然后去分治
这种分治的过程很像线段树的查询,复杂度计算也可以参考
cdq维护左区间对右区间的影响实际上是对于左区间维护了一个数据结构(并查集)


并查集维护了rt 该连通块的根 以及 fl 该连通块是否成环了
复杂度应该是O(NlogNlogN)的,但是由于此类题目一般借助LCT,LCT不管怎么写,常数都不小,所以亲测差距不大


学完了发现LCT还有一个最大删除时间生成树,这东西也是妙啊,mark一下

就还是这类题,有删除时间,对于非树边,如果他将来还要作为非树边,这是十分麻烦的,所以就将环上的最小删除时间的边抠出来删去,把新的边接上,这样就保证从边上换下来的边都不会被再作为树边,这样的话这种类型的题就很好办了,可以方便的用LCT的一棵树维护出一个图。


#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <cmath>
#include <map>
#include <string>
#include <iostream>
#include <queue>
#define pii pair<int,int>
#define xx first
#define yy second
#define mp make_pair
#define lson l, mid, x<<1
#define rson mid+1, r, x<<1|1
#define eps 1e-9
#define uint unsigned
using namespace std;
typedef long long ll;
const int N = 4e5+5;
int siz[N], fa[N], rt[N], a[N], cnt = 0, tim[N], q[N], n;
bool fl[N];
vector<int>ans;
struct edge
{
    int x, y, s, e;
    edge(){}
    edge(int _x, int _y, int _s, int _e){
        x = _x; y = _y; s = _s; e = _e;
    }
};

struct node
{
    int id, siz, fa, rt; bool fl;
    node(){};
    node( int _id, int _siz, int _fa, int _rt, bool _fl ){
        id = _id; siz = _siz; fa = _fa; rt = _rt; fl= _fl;
    }
}stk[N*30];

void re( node p )
{
    int id = p.id;
    siz[id] = p.siz;
    fa[id] = p.fa;
    rt[id] = p.rt;
    fl[id] = p.fl;
}

void init( int n )
{
    for( int i = 1; i <= n; i ++ ){
        fa[i] = i;
        rt[i] = i;
        siz[i] = 1;
        fl[i] = 0;
    }
}

int find( int x )
{
    if( fa[x] == x ) return x;
    else return find(fa[x]);
}

bool merge( int x, int y )//x->y
{
    int fx = find(x), fy = find(y);
    stk[cnt++] = node( fx, siz[fx], fa[fx], rt[fx], fl[fx] );
    if( fx == fy ){
        fl[fx] = 1;
        return 0;
    }
    stk[cnt++] = node( fy, siz[fy], fa[fy], rt[fy], fl[fy] );
    if( siz[fx] < siz[fy] ){
        fa[fx] = fy;
        siz[fy] += siz[fx];
    }
    else{
        fa[fy] = fx;
        siz[fx] += siz[fy];
    }
    if( fl[fx] || fl[fy] ) fl[fx] = fl[fy] = 1;
    rt[fx] = rt[fy];
    return 1;
}

void cdq( int l, int r, vector<edge>& g )
{
    int i, j, k, nw = cnt;
//    puts("");
//    printf("%d %d\n", l, r);
    for( auto& v : g ){
        if( v.s == l && v.e == r ){
            //printf("%d %d %d %d\n", v.x, v.y, v.s, v.e);
            merge( v.x, v.y );
        }
    }
    //for( i = 1; i <= n; i ++ ) printf("%d %d %d %d %d\n", i, fa[i], siz[i], rt[i], fl[i] );
    if( l == r ){
        if( q[l] ){
            int v = q[l];
            int fx = find(v);
            if( fl[fx] == 1 ) ans.push_back(-1);
            else{
                ans.push_back(rt[fx]);
            }
        }
        return ;
    }
    vector<edge>L, R;
    int mid = (l+r)>>1;
    for( auto v : g ){
        if( v.s == l && v.e == r ) continue;// !!!
        if( v.e <= mid ) L.push_back(v);
        else if( v.s > mid ) R.push_back(v);
        else{
            L.push_back( edge( v.x, v.y, v.s, mid ) );
            R.push_back( edge( v.x, v.y, mid+1, v.e ) );
        }
    }
    cdq( l, mid, L );
    cdq( mid+1, r, R );
    while( cnt > nw ){ re( stk[cnt-1] ); cnt --; }
    return ;
}

int main()
{
    int m, i, j, k, x, y;
//    freopen("in.txt", "r", stdin);
//    freopen("wrong.txt", "w", stdout);
    scanf("%d%d", &n, &m);
    init( n );
    for( i = 1; i <= n; i ++ ){
        scanf("%d", &a[i]);
        tim[i] = 0;
    }
    vector<edge>ed;
    for( i = 1; i <= m; i ++ ){
        int id;
        scanf("%d", &id);
        if( id == 1 ){
            scanf("%d%d", &x, &y);
            if( a[x] ){
                ed.push_back( edge( x, a[x], tim[x], i-1 ) );
            }
            a[x] = y;
            tim[x] = i;
        }
        else{
            scanf("%d", &x);
            q[i] = x;
        }
    }
    for( i = 1; i <= n; i ++ ){
        if( a[i] ){
            ed.push_back( edge( i, a[i], tim[i], m+1 ) );
        }
    }
    //for( auto v : ed ) printf("%d %d %d %d\n", v.x, v.y, v.s, v.e);
    cdq( 0, m+1, ed );
    for( auto& v : ans ) printf("%d\n", v);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值