【线段树分治】luogu_P5787 二分图 /【模板】线段树分治
题意
有一个\(n\)个节点的图。
在\(k\)时间内有\(m\)条边会出现后消失。
要求出每一时间段内这个图是否是二分图。
\(n,k=10^5,m=2\times 10^5\)。
思路
离线算法。根据时间来分治。
用线段树来保存覆盖了时间段\([l,r]\)的边。
对于当前线段树节点,如果这些边都添➕进图中会使这个图不是二分图,就直接退出。
如果边都➕完了,我们就继续分治,处理没有完全覆盖这个区间的边。
这就相当于在线段树上分治,即线段树分治。
至于判断二分图,可以使用扩展域并查集来处理,并且要支持撤销操作,这个用栈维护再回溯即可。
代码
#include <cstdio>
#include <vector>
int n, m, k, cnt, top;
int u[300001], v[300001], fa[600001], height[600001];
int V[300001];
std::vector<int> dat[1200001];
std::pair<int, int> st[600001];
void insert(int p, int l, int r, int L, int R, int x) {
if (L <= l && r <= R) {
dat[p].push_back(x);
return;
}
int mid = l + r >> 1;
if (L <= mid)
insert(p << 1, l, mid, L, R, x);
if (R > mid)
insert(p << 1 | 1, mid + 1, r, L, R, x);
}
int find(int x) {//注意并查集用按秩合并,路径压缩会影响回溯
return x == fa[x] ? x : find(fa[x]);
}
void merge(int x, int y) {
int f1 = find(x), f2 = find(y);
if (height[f1] > height[f2])
std::swap(f1, f2);
st[++top] = std::make_pair(f1, height[f1] == height[f2]);
fa[f1] = f2;
height[f2] += height[f1] == height[f2];
}
void solve(int p, int l, int r) {
int flag = 1, lastop = top;
for (int i = 0; i != dat[p].size(); i++) {
int tmp = dat[p][i];
int f1 = find(u[tmp]), f2 = find(v[tmp]);
if (f1 == f2) {//不是二分图就不用继续分治,这个时间段都不是二分图
for (int j = l; j <= r; j++)
printf("No\n");
flag = 0;
break;
}
merge(u[tmp], v[tmp] + n);//扩展域
merge(u[tmp] + n, v[tmp]);
}
if (flag) {//覆盖当前区间的边可以
if (l == r)
printf("Yes\n");
else {//继续分治
int mid = l + r >> 1;
solve(p << 1, l, mid);
solve(p << 1 | 1, mid + 1, r);
}
}
while (top > lastop) {
height[fa[st[top].first]] -= st[top].second;
fa[st[top].first] = st[top].first;
top--;
}
}
int main() {
scanf("%d %d %d", &n, &m, &k);
for (int i = 1; i <= n; i++)
fa[i] = i, fa[i + n] = i + n;
for (int i = 1, l, r; i <= m; i++) {
scanf("%d %d %d %d", &u[i], &v[i], &l, &r);
if (l ^ r)
insert(1, 1, k, l + 1, r, i);
}
solve(1, 1, k);
}