洛谷 P5787 二分图 /【模板】线段树分治 题解

一、题目:

洛谷原题

二、思路:

这道题是线段树分治的模板题。

首先考虑一个问题,就是如何快速判断一张图是不是二分图。只需判断这张图中是否存在奇环。我们可以使用“边带权”并查集去解决。具体来说,我们维护一个数组\(d\)。这个数组的功能是这样的:

如果我们想知道\(x\)\(y\)之间的路径边数的奇偶性(当然\(x\)\(y\)已经联通),只需分别算出\(x\)\(y\)到并查集的根的路径的亦或和\(d_1\)\(d_2\)。再将\(d_1\oplus d_2\),即可得到\(x\)\(y\)之间路径边数的奇偶性。

再来考虑本题怎么做。我们发现一个询问的答案只和包含该询问的操作有关,像极了线段树上区间操作、单点查询的特点。所以不难得出以下算法。

对于每一个操作,我们先在定义在\([1, K]\)的线段树的对应位置插入询问。也就是说一个询问最多被分成\(\log_2 len\)块。

等到所有的操作全部被插入完成后,我们考虑对每个节点进行询问。对整棵线段树进行一遍dfs。加入我们访问到叶子结点\(x\)(也就表示我们要回答时间段\(x\)的询问了),我们只需将根到\(x\)路径上的所有操作造成的影响累计起来,就可以回答出\(x\)的询问了。

再考虑我们访问结束一个点\(x\),该怎么撤销挂在这个点上的操作的影响。我们发现如果用路径压缩的并查集,是很难进行撤销的。所以需要使用按秩合并并查集。每次用一个栈记录并查集上哪些节点的父亲受到改变,撤销时依次从栈中弹出,进行相应的撤销即可。这个思路在回滚莫队中也曾用到。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;
inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 1e5 + 5;

int n, m, K, fa[maxn], d[maxn], siz[maxn];
int L[maxn << 2], R[maxn << 2];
int stack[maxn], top;

struct Update {
    int x, y, id;
};

vector<Update> upd[maxn << 2];

int find(int x, int& nowd) {
    if (x == fa[x]) { nowd = 0; return x; }
    int tmp = find(fa[x], nowd);
    nowd ^= d[x];
    return tmp;
}

inline bool merge(int x, int y) {
    int d1, d2;
    x = find(x, d1); y = find(y, d2);
    if (x == y) {
        if (d1 ^ d2) return true;
        return false;
    }
    else {
        if (siz[x] > siz[y]) swap(x, y), swap(d1, d2);
        fa[x] = y;
        siz[y] += siz[x];
        d[x] = d1 ^ d2 ^ 1;
        stack[++top] = x;
        return true;
    }
}

#define lson (o << 1)
#define rson (o << 1 | 1)

void build(int o, int l, int r) {
    L[o] = l; R[o] = r;
    if (l == r) { return; }
    int mid = (l + r) >> 1;
    build(lson, l, mid); build(rson, mid + 1, r);
}

inline void insert(int o, int ql, int qr, const Update& q) {
    if (ql <= L[o] && R[o] <= qr) { upd[o].push_back(q); return; }
    int mid = (L[o] + R[o]) >> 1;
    if (ql <= mid) insert(lson, ql, qr, q);
    if (qr > mid) insert(rson, ql, qr, q);
}

inline void dfs(int o, bool now) {
    int lastop = top;
    for (auto& p : upd[o]) {
        if (!merge(p.x, p.y)) now = false;
    }
    if (L[o] == R[o]) {
        puts(now ? "Yes" : "No");
        return;
    }
    dfs(lson, now); dfs(rson, now);
    while (top > lastop) {// 依次撤销栈中元素的影响。
        int x = stack[top--];
        siz[fa[x]] -= siz[x];
        d[x] = 0;
        fa[x] = x;
    }
}

int main() {
    n = read(); m = read(); K = read();
    for (int i = 1; i <= n; ++i) fa[i] = i, siz[i] = 1;
    build(1, 1, K);
    for (int i = 1; i <= m; ++i) {
        Update q;
        q.id = i;
        q.x = read(); q.y = read();
        int l = read() + 1, r = read();
        insert(1, l, r, q);
    }
    dfs(1, 1);
    return 0;
}

posted @ 2021-04-04 10:21  蓝田日暖玉生烟  阅读(71)  评论(0编辑  收藏  举报