“今日头条杯”首届湖北省大学程序设计竞赛 Avengers: Infinite War(CDQ分治+并查集)

J - Avengers: Infinite War

题意:
             n n 个节点m个操作,操作分四种:

  1. 1 x y: 1   x   y : (x,y) ( x , y ) 节点连接一条边
  2. 2 x y 2   x   y : 断开 (x,y) ( x , y ) 这条边
  3. 3 x 3   x : 查询 x x 所在的树的大小
  4. 4 x y xy x , y 是否在同一棵树中

思路:
             cdq c d q 分治 + + 并查集,原来以为是动态树, 写完了发现不知道怎么计算大小。。。可以考虑对整个操作序列分治,当处理[l,r]这个区间的答案的时候,我们可以先递归处理 [l,mid] [ l , m i d ] 这部分,一直到 l==r l == r ,然后处理 [mid+1,r] [ m i d + 1 , r ] 这个区间, 处理这个区间的时候,要先处理一些操作:
             如果 (x,y) ( x , y ) 这条边的处理在 [l,r] [ l , r ] 出现过, 但是却没有在 [mid+1,r] [ m i d + 1 , r ] 这个区间出现过时候, solve(mid+1,r) s o l v e ( m i d + 1 , r ) 可以直接考虑最后出现的 (x,y) ( x , y ) 是断开还是连接着的,断开不处理,连接的直接并查集合并。
             如果 (x,y) ( x , y ) 这条边的处理在 [l,r] [ l , r ] 出现过, 在 [mid+1,r] [ m i d + 1 , r ] 这个区间也出现过的时候,如果在 [l,mid] [ l , m i d ] 里面 (x,y) ( x , y ) 最后一次是断开着的, 那么也不用处理, 直接 solve(mid+1,r) s o l v e ( m i d + 1 , r ) 但是在 [l,mid] [ l , m i d ] 最后一次是连接着的情况, 这个时候可能会影响 [mid+1,r] [ m i d + 1 , r ] 里面的一些区间, 因为这些操作都是合法操作, 那么我们知道如果 [mid+1,r] [ m i d + 1 , r ] 里面的一条边的操作是先断开的话,假设在 idx i d x 这个位置首次断开 (x,y) ( x ′ , y ′ ) 这条边,那么可以知道 [mid+1,idx] [ m i d + 1 , i d x ] 这些操作的时候 (x,y) ( x ′ , y ′ ) 都是连接着的,所以只要是这种情况, 任意区间 [l,r] [ l , r ] 可以在 solve(l,r) s o l v e ( l , r ) 的时候先处理这种情况,即:如果在 [l,r] [ l , r ] 中有边的处理有首次是断开而且下标在 [mid+1,r] [ m i d + 1 , r ] 之间,这个时候这两个点可以先连接起来再处理 [l,mid] [ l , m i d ] ,等处理完 [l,mid] [ l , m i d ] 之后再恢复这些状态, 再去处理 [mid+1,r] [ m i d + 1 , r ] 。并查集状态恢复不太好操作, 一个比较好的办法就是, 记住所有的修改序列,到时候逆向恢复状态就好了。


#include<bits/stdc++.h>
typedef long long ll;
const int maxn = 2e5 + 10;
using namespace std;

struct P {
    int op, x, y, id;
    P(int o = 0, int x = 0, int y = 0, int id = 0) :
        op(o), x(x), y(y), id(id) {}
    void input(int i) {
        id = i;
        scanf("%d", &op);
        if(op != 3) {
            scanf("%d %d", &x, &y);
            if(x > y) swap(x, y);
        } else { scanf("%d", &x); y = -1; }
    }
    bool operator < (P p) const {
        if(x != p.x) return x < p.x;
        if(y != p.y) return y < p.y;
        return id < p.id;

    }
} rec[maxn], res[maxn], rs[maxn];

struct V {
    int flag, vt, val;
    V(int f = 0, int vt = 0, int v = 0) : flag(f), vt(vt), val(v) {}
};

int ans[maxn], n, m, tot;
int pre[maxn], sz[maxn];
V vec[maxn * 30];

int findset(int x) {
    if(x == pre[x]) return x;
    int px = findset(pre[x]);
    vec[tot++] = V(0, x, pre[x]); pre[x] = px;
    return pre[x];
}

void return_old(int ssz) {
    while(tot > ssz) {
        tot--;
        int flag = vec[tot].flag;
        int old = vec[tot].val;
        int v = vec[tot].vt;
        if(flag == 0) pre[v] = old;
        else sz[v] = old;
    }
}

void Merge(int x, int y) {
    int nx = findset(x);
    int ny = findset(y);
    if(nx == ny) return ;
    vec[tot++] = V(1, nx, sz[nx]);
    vec[tot++] = V(0, ny, pre[ny]);
    sz[nx] += sz[ny]; pre[ny] = nx;
}

void solve(int l, int r) {
    int now_sz = tot, mid = (l + r) >> 1;
    if(l == r && rec[l].op <= 2) return ;  ///只是处理边
    int num = 0;
    for(int i = l; i <= r; i++) {
        if(rec[i].op >= 3) continue;
        rs[num++] = rec[i];
    }
    sort(rs, rs + num);
    for(int i = 0; i < num; i++) {
        int from = i;
        while(from < num && rs[from].x == rs[i].x && rs[from].y == rs[i].y) from++;
        ///区间[l, r]中第一条边(x,y)是断边操作,也就是说这个时候x,y是连接着的,把(x,y)影响的[l,mid]的区间先合并一下
        if(rs[i].id > mid && rs[i].op == 2) Merge(rs[i].x, rs[i].y);
        i = from - 1;
    }
    if(l == r) {
        if(rec[l].op == 3) {
            int nx = findset(rec[l].x);
            ans[l] = sz[nx];
        } else if(rec[l].op == 4) {
            int nx = findset(rec[l].x);
            int ny = findset(rec[l].y);
            ans[l] = (nx == ny);
        }
        return_old(now_sz);
        return ;
    }

    solve(l, mid);
    return_old(now_sz);
    int cnt = 0;
    for(int i = l; i <= r; i++) {
        if(rec[i].op == 3 || rec[i].op == 4) continue;
        res[cnt++] = rec[i];
    }
    sort(res, res + cnt);
    for(int i = 0; i < cnt; i++) {
        int from = i, flag = 0;
        while(from < cnt && res[from].x == res[i].x && res[from].y == res[i].y) {
            if(res[from].id > mid) flag = 1;
            from++;
        }
        i = from - 1;
        if(!flag && res[i].op == 1) { ///后面已经没有处理这条边的操作了
            int nx = findset(res[i].x);
            int ny = findset(res[i].y);
            if(nx != ny) {
                vec[tot++] = V(1, nx, sz[nx]);
                vec[tot++] = V(0, ny, pre[ny]);
                sz[nx] += sz[ny]; pre[ny] = nx;
            }
        }
    }
    solve(mid + 1, r);
    return_old(now_sz);
}
int fac[maxn];

int main() {
    while(scanf("%d %d", &n, &m) != EOF) {
        for(int i = 1; i <= n; i++) { sz[i] = 1; pre[i] = i; }
        for(int i = 0; i < m; i++) {
            rec[i].input(i);
            fac[i] = rec[i].op;
        }
        solve(0, m - 1);
        for(int i = 0; i < m; i++) {
            if(fac[i] == 3) printf("%d\n", ans[i]);
            else if(fac[i] == 4) puts(ans[i] ? "Yes,cap" : "No");
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值