JZOJ4769 【GDOI2017模拟9.9】graph CDQ分治+用按秩合并维护带撤销的并查集(BZOJ 4025)

题目大意

现在有 N 个点,M个操作,对于每个操作有两种情况
1. Ord=1 :读入 u,v 表示连接 u,v
2. Ord=0 :读入 u 表示删除第u次连接的边。
对于每次操作,如果当前是一幅二分图则输出 YES ,否则输出 NO

N,M3105

解题思路

我们先考虑没有删除的情况,判断当前的图是不是一个二分图就相当与判断图中有没有奇环。这个可以用并查集集很轻松的维护(直接记录一下当前点与他祖先是否在二分图的一边,每次找父亲的时候更新一下)。

现在增加了删除操作,我们发现在线很难维护(好像用LCT可以做),那么我们就想想离线的做法。我们可以把第 i 个操作设为时间i,那么我们就知道每一条边的存在时间。那么对于一个时间段 (L,R) 我们想要知道对这段区间有影响的边集 E ,这个可以用CQD分治实现。设 (L,R,E) 表示时间在区间 (L,R) ,边集为 E 时二分图的情况(一样用并查集维护),我们只需每次把符合要求的边分到两边就可以实现E的维护。那么现在有一个问题就是我们递归进一个状态后怎么撤销这个时间区间的边的影响。

由于如果并查集加了路径压缩优化后就会丢失中间的信息,所以我们考虑只用按秩合并在 LogN 的时间内维护并查集,然后对于每个 CDQ 分治的每个状态记录下这一层对当前二分图的影响,退出时再恢复就可以了。

程序

#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 3e5 + 5;

struct Node {
    int u, v, l, r;
    Node (int a, int b, int c, int d) {u = a, v = b, l = c, r = d;}
    Node () {}
} D[MAXN];

int N, M, tot, Ans[MAXN], Save[MAXN], Cnt, Fa[MAXN], Val[MAXN], Deep[MAXN];

int Get(int u) {return (Fa[u] == u) ? u : Get(Fa[u]);}

int GetVal(int u) {
    int Ans = 0;
    for (; u != Fa[u]; u = Fa[u]) Ans ^= Val[u];
    return Ans;
}

void Back(int tag) {
    for (; Cnt != tag; Cnt --) {
        if (Save[Cnt] < 0) Deep[-Save[Cnt]] --; else {
            Fa[Save[Cnt]] = Save[Cnt];
            Val[Save[Cnt]] = 0;
        }
    }
}

void Push(int u, int v, int val) {
    if (Deep[u] > Deep[v]) swap(u, v);
    if (Deep[u] == Deep[v]) {
        Deep[v] ++;
        Save[++ Cnt] = -v;
    }
    Fa[u] = v, Val[u] = val;
    Save[++ Cnt] = u;
}

void Solve(int L, int R, int Num) {
    int tag = Cnt;
    for (int i = 1; i <= Num; i ++) {
        int u = D[i].u, v = D[i].v;
        if (D[i].l <= L && D[i].r >= R) {
            int fu = Get(u), fv = Get(v);
            int Val = GetVal(u) ^ GetVal(v) ^ 1;
            if (fu == fv) {
                if (Val) {
                    for (int j = L; j <= R; j ++) printf("NO\n");
                    Back(tag);
                    return;
                }
            }
            Push(fu, fv, Val);
            swap(D[i --], D[Num --]);
        }
    }
    if (L == R) {
        printf("YES\n");
        Back(tag);
        return;
    }
    int Mid = (L + R) >> 1, Lim = Num;
    for (int i = 1; i <= Lim; i ++) 
        if (D[i].l > Mid) swap(D[i --], D[Lim --]);
    Solve(L, Mid, Lim);
    Lim = Num;
    for (int i = 1; i <= Lim; i ++)
        if (D[i].r <= Mid) swap(D[i --], D[Lim --]);
    Solve(Mid + 1, R, Lim);
    Back(tag);
}

int main() {
    scanf("%d%d", &N, &M);
    for (int i = 1; i <= M; i ++) {
        int Ord;
        scanf("%d", &Ord);
        if (Ord == 1) {
            int u, v;
            scanf("%d %d\n", &u, &v);
            u ++, v ++;
            D[++ tot] = Node(u, v, i, N);
        } else {
            int id;
            scanf("%d\n", &id);
            D[++ id].r = i - 1;
        }
    }
    for (int i = 1; i <= N; i ++) Fa[i] = i;
    Solve(1, M, tot);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值