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 2≤n,q≤105)
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);
}