洛谷 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;
}