CF EDU 22 F. Bipartite Checking (时间分治线段树+可撤销并查集+二分图判定)

CF EDU 22 F. Bipartite Checking

∗ 2500 *2500 2500

题意:

You are given an undirected graph consisting of n n n vertices. Initially there are no edges in the graph. Also you are given q q q queries, each query either adds one undirected edge to the graph or removes it. After each query you have to check if the resulting graph is bipartite. ( 2 ≤ n , q ≤ 1 0 5 2\leq n,q\leq 10^5 2n,q105)

INPUT(if there is an edge between vertices xi and yi, then remove it, otherwise add it.)
3 5
2 3
1 3
1 2
1 2
1 2
OUTPUT
YES
YES
NO
YES
NO

题解:

知识点:时间分治线段树+可撤销并查集+二分图判定

基于官方Tutorial

如果边只增不减的话,我们可以使用并查集解决,并查集维护该点属于的leader以及它到leader的距离。然后,当我们试图连接的两个点有共同的leader时,如果它们到leader的距离和为偶数,那么此时环的长度为奇数,该图不是二分图

接下来的算法将考虑如何从DSU中删除最后加进来的一条边(或一些边)。如何实现呢?当每次我们需要修改DSU中一些变量时,我们可以将这些变量的地址以及先前的值存储在某个位置(例如在堆栈st中)。然后,当要删除最后新加的边时,我们再将这些变量恢复到上一个的状态。

现在,我们的算法支持在 O ( log ⁡ n ) O(\log n) O(logn)的时间内增加边和删除最后加入的边。 O ( log ⁡ n ) O(\log n) O(logn)是因为不支持路径压缩(因为需要回退的缘故),只支持按秩合并。

下面,我们将真正解决这个问题。

为了方便起见,我们将所有的输入信息改为这样的形式:“从查询时间 l l l到查询时间 r r r,边 ( x , y ) (x, y) (x,y)存在”。新的形式的信息数至多为 q q q。根据此信息构建时间分治线段树ins()。使用分治的方法设计一个函数ask,返回值为查询每个时间段 [ a , b ] [a,b] [a,b]图是否是二分图的结果。首先,我们所有出现在该时间段中的边加入DSU(实际只需加入当前还未加入的,如可能在 [ 1 , 10 ] [1,10] [1,10]中已经加入过了的边,在 [ 1 , 5 ] [1,5] [1,5]中就不用再加入了)。然后我们再递归的解决时间段 [ a , ⌊ a + b 2 ⌋ ] , [ ⌊ a + b 2 ⌋ , b ] [a,\lfloor{ a+b\over 2}\rfloor],[\lfloor{a+b\over 2}\rfloor,b] [a,2a+b],[2a+b,b]。当我们处理到时间段 [ a , a ] [a,a] [a,a],此时增加当前时间段内有的边,我们就可以得到时间点 a a a是需要的答案。记得在递归函数返回前,将递归函数所在是时间区间 [ l , r ] [l,r] [l,r]中做的所有修改进行还原(即实现一个可撤销带权并查集)。最后,我们可以通过在区间 [ 1 , q ] [1,q] [1,q]上调用该函数来获得答案。

时间复杂度为 O ( q log ⁡ 2 q ) O(q\log ^2 q) O(qlog2q),因为每一条边在函数中只增加 O ( log ⁡ q ) O(\log q) O(logq)次。

Note! l o g 2 x log^2x log2x means ( l o g ( x ) ) 2 (log(x))^2 (log(x))2. See more information here.

详细实现细节请查看代码,有详细注释。

#include <bits/stdc++.h>
using namespace std;
#define mk make_pair
#define pint pair<int, int>
const int MAXN = 100005;

int n, q;
int tp;                      // stack top point
int fa[MAXN];                // DSU father
int dep[MAXN];               // DSU rank
int c[MAXN];                 // the distance to the father is odd(1) or even(0), pay attention, not to the leader
int st[MAXN * 2];            // store the changes, positive number means change in array fa and c, negative number means change in array dep, absolute value indicates the modification position
map<pint, int> mp;           // tool : store the occurrence time of the edge
vector<pint> vec[4 * MAXN];  // segment tree

int find(int x) {
    return x == fa[x] ? x : find(fa[x]);
    /*  can't use path compress like : return x == fa[x] ? x : fa[x] = find(fa[x]);
        because every changes in fa need to stored in st.
        you can find the reason for details using the Test #2
    */
}

// return the distance to the leader
int dis(int x) {
    return x == fa[x] ? 0 : (c[x] ^ dis(fa[x]));
}

void merge(int u, int v, int ct) {
    // both u and v are leaders
    if (dep[u] < dep[v])
        swap(u, v);  //merge by rank
    fa[v] = u;
    c[v] = ct;
    if (dep[u] == dep[v]) st[++tp] = -u, dep[u]++;
    st[++tp] = v;
}

void back(int t) {
    // Undo the operation in st until the tp is t
    while (tp > t) {
        if (st[tp] < 0)
            dep[-st[tp]]--;
        else
            fa[st[tp]] = st[tp], c[st[tp]] = 0;
        tp--;
    }
}

void ask(int i, int l, int r) {
    //node, query l, query r
    int cur = tp;  // stack point in this function
    for (int j = 0; j < vec[i].size(); j++) {
        int x = vec[i][j].first, y = vec[i][j].second;
        int flag = dis(x) ^ dis(y) ^ 1;  //flag equals to the distance from x leader to y leader
        if (find(x) == find(y)) {
            if (flag) {
                // graph const of these edges isn't bipartite in segment [l,r]
                for (int i = l; i <= r; i++)
                    puts("NO");
                back(cur);  // get rid of the edges that already added before return
                return;
            }
        } else {
            merge(find(x), find(y), flag);
        }
    }
    if (l == r) {
        puts("YES");
        back(cur);
        return;
    }
    int mid = (l + r) >> 1;
    ask(i << 1, l, mid);          // handle smaller segment, note that the edges in segment [l,r]
    ask(i << 1 | 1, mid + 1, r);  // will still in segment [l,mid] and [mid+1,r]
    back(cur);
}

void ins(int i, int l, int r, int L, int R, pint x) {
    // node, border l, border r, now L,now R, edge
    if (L > r || R < l) return;
    if (L <= l && R >= r) {
        vec[i].push_back(x);
        return;
    }
    int mid = (l + r) >> 1;
    ins(i << 1, l, mid, L, R, x);
    ins(i << 1 | 1, mid + 1, r, L, R, x);
}

int main() {
    cin >> n >> q;
    for (int i = 1; i <= n; i++) fa[i] = i;
    // build the segment tree
    for (int i = 1; i <= q; i++) {
        int u, v;
        cin >> u >> v;
        pint t = mk(u, v);
        if (mp.count(t)) {
            ins(1, 1, q, mp[t], i - 1, t);
            mp.erase(t);
        } else
            mp[t] = i;
    }
    for (auto item : mp)
        ins(1, 1, q, item.second, q, item.first);
    // query answer with divide and conquer idea
    ask(1, 1, q);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值