[BZOJ3237][Ahoi2013]连通图(并查集+CDQ分治)

考虑只有两个集合 A A B的情况。
(1)把不属于 A A 也不属于B的边加上去。
(2)求集合 A A 的答案时,就在(1)的基础上把属于B但不属于 A A 的边加上去并判定。
(3)求集合B的答案时,就在(1)的基础上把属于 A A 但不属于B的边挤上去并判定。
(2)(3)都是在(1)的基础上加边的,所以要写一个可持久化支持撤回上一次操作的并查集来实现(记录下每次father改变的位置,就可以支持撤回)。
回到原问题。由于题目允许离线,所以使用CDQ分治。先把所有集合都没有的边加上。
假设现在递归到询问区间 [l,r] [ l , r ] ,并且不属于询问区间 [l,r] [ l , r ] 的集合的边已经加上。分为两个子区间 [l,mid] [ l , m i d ] [mid+1,r] [ m i d + 1 , r ]
(1)处理 [l,mid] [ l , m i d ] :把属于 [mid+1,r] [ m i d + 1 , r ] 但不属于 [l,mid] [ l , m i d ] 的边加上,并递归到 [l,mid] [ l , m i d ] 。这一步的加边操作,在操作完之后需要撤回。
(2)处理 [mid+1,r] [ 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 ] 。同样,这一步的加边操作,在操作完之后也需要撤回。
剩下最后一个问题:递归到 [i,i] [ i , i ] 时,如何判断图的连通性。
由于图原本是连通的,所以递归到 [i,i] [ i , i ] 时,只需要对于集合 i i 的每条边(u,v),判断 u u v是否都连通即可。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e5 + 5, M = 2e5 + 5, V = 5, Orz = 5e6 + 5;
int n, m, K, fa[N], X[M], Y[M], q[M][V], pyz[Orz], lpf[Orz],
tim[Orz], times, orz; bool ans[M], is[M];
int cx(int x, bool ad) {
    if (!ad) {
        while (fa[x] != x) x = fa[x]; return x;
    }
    if (fa[x] != x)
        pyz[++orz] = x, lpf[orz] = fa[x],
        tim[orz] = times, fa[x] = cx(fa[x], ad);
    return fa[x];
}
void zm(int x, int y) {
    times++; int ix = cx(x, 1), iy = cx(y, 1);
    if (ix != iy) pyz[++orz] = iy, lpf[orz] = fa[iy],
        tim[orz] = times, fa[iy] = ix;
}
void backto(int tar) {
    while (tim[orz] > tar) fa[pyz[orz]] = lpf[orz], orz--;
    times = tar;
}
void solve(int l, int r) {
    int i, j; if (l == r) {
        bool flag = 1; for (i = 1; i <= q[l][0]; i++)
        flag = flag && cx(X[q[l][i]], 0) == cx(Y[q[l][i]], 0);
        ans[l] = flag; return;
    }
    int mid = l + r >> 1;
    for (i = l; i <= r; i++) for (j = 1; j <= q[i][0]; j++) is[q[i][j]] = 0;
    for (i = l; i <= mid; i++) for (j = 1; j <= q[i][0]; j++)
        is[q[i][j]] = 1; int tmp = times; for (i = mid + 1; i <= r; i++)
    for (j = 1; j <= q[i][0]; j++) if (!is[q[i][j]])
        zm(X[q[i][j]], Y[q[i][j]]); solve(l, mid); backto(tmp);
    for (i = l; i <= r; i++) for (j = 1; j <= q[i][0]; j++) is[q[i][j]] = 0;
    for (i = mid + 1; i <= r; i++) for (j = 1; j <= q[i][0]; j++)
        is[q[i][j]] = 1; tmp = times; for (i = l; i <= r; i++)
    for (j = 1; j <= q[i][0]; j++) if (!is[q[i][j]])
        zm(X[q[i][j]], Y[q[i][j]]); solve(mid + 1, r); backto(tmp);
}
int main() {
    int i, j; n = read(); m = read();
    for (i = 1; i <= m; i++) X[i] = read(), Y[i] = read();
    K = read(); for (i = 1; i <= K; i++) {
        q[i][0] = read(); for (j = 1; j <= q[i][0]; j++)
            is[q[i][j] = read()] = 1;
    }
    for (i = 1; i <= n; i++) fa[i] = i;
    for (i = 1; i <= m; i++) if (!is[i]) zm(X[i], Y[i]); solve(1, K);
    for (i = 1; i <= K; i++) puts(ans[i] ? "Connected" : "Disconnected");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值