线段树分治
引子
对于这类的问题
- 有一些操作,每个操作只在 \(l \sim r\) 时间段内有效。
- 有一些询问,每个询问某一个时间点所有操作的贡献。
对于这样的询问,可以对时间建一棵线段树,对于每个操作,相当于在线段树上做区间操作
遍历整颗线段树,到达每个节点时执行相应的操作,然后继续向下递归,到达叶子节点时统计贡献,回溯时撤销操作即可。
这样的思想被称为线段树分治,可以在低时间复杂度内解决一类在线算法不优秀的问题。
如何判断是否为二分图:
二分图:把图中所有点分成两个集合,使得所有边的两个顶点不在一个集合中。也就是一个不存在奇环的图
常用的方法是染色:如果是二分图,那么一条边的两个顶点一定是不同集合的,也就是颜色不同
还有一种方法就是值域并查集
- 扩展域并查集:对于一个节点 \(i\) ,我们将其拆分为两个节点。一个属于集合 \(S\) ,另一个属于集合 \(T\) 。那么一条边所连接的两个节点就必须在不同的集合中。一个点在 \(S\) 中和在 \(T\) 的两个点属于一个集合,那么这张图就不是二分图。
对于时间端:
我们按照上面,对时间段建立线段树,每个节点开一个 vector,存下这个时间端内存在的边,遍历时,从根节点出发,每到一个节点,将挂在该节点上的所有边合并,然后递归处理左儿子和右儿子。如果发现有某条边合并会出现奇环,那么当前线段树节点所对应的时间区间都不会形成二分图。
当到达叶子节点时,如果合并了所有挂在当前节点上的边,依旧满足二分图的性质,那这个时刻就可以形成二分图。
回溯的时候,因为并查集不能删边,用可撤销并查集(按秩合并),用栈维护并查集的操作就好了。
按秩合并??:把高度小的树合并到高度大的树上,如果高度一样,合并后高度就 +1
保证了树的高度最多为 log n
按秩合并复杂度:\(O(log~n)\),线段树:\(O(m~log~k)\)
总的复杂度:\(O(logn\times mlogn)\)
code
/*
work by:Ariel_
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <stack>
#include <vector>
#include <utility>
#define ll long long
#define rg register
#define lson rt << 1
#define rson rt << 1|1
using namespace std;
const int N = 1e5 + 5;
const int M = 2e5 + 5;
int read(){
int x = 0,f = 1; char c = getchar();
while(c < '0'||c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') {x = x*10 + c - '0'; c = getchar();}
return x*f;
}
struct node{int x, fag;};
int n, m, fa[N << 1], a[N << 1], k, h[N << 1], u[M], v[M];
struct Tree{int l, r; vector<int> e;}tree[N << 3];
stack<node> s;
int get_father(int x) {
while(x ^ fa[x]) x = fa[x];
return x;
}
void merge(int x, int y) {
if (x == y) return ;
if (h[x] > h[y]) swap(x, y);
s.push((node){x, h[x] == h[y]}), fa[x] = y, h[y] += h[x] == h[y]; //如果两棵树高度相同,那么合并之后高度会 + 1
}
void build(int rt, int l, int r) {
tree[rt].l = l, tree[rt].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
}
void ins(int rt, int L, int R, int id) {
if (L <= tree[rt].l && tree[rt].r <= R) {
tree[rt].e.push_back(id);
return;
}
int mid = (tree[rt].l + tree[rt].r) >> 1;
if (L <= mid) ins(lson, L, R, id);
if (R > mid) ins(rson, L, R, id);
}
void DFS(int rt, int l, int r) {
bool op = 1;//判断合不合法
int mid = (l + r) >> 1, siz = (int)s.size();
for (int i = 0; i < (int)tree[rt].e.size(); i++) {
int x = tree[rt].e[i], u = get_father(::u[x]), v = get_father(::v[x]);
if (u == v) {
for (int i = l; i <= r; i++) printf("No\n");
op = 0; break;
}
merge(u, get_father(::v[x] + N)), merge(v, get_father(::u[x] + N));
}
if (op) {
if (l == r) printf("Yes\n");
else DFS(lson, l, mid), DFS(rson, mid + 1, r);
}
while (s.size() > siz) {
node t = s.top();
int x = t.x, fag = t.fag;
h[fa[x]] -= fag, fa[x] = x, s.pop();
}
}
int main(){
n = read(), m = read(), k = read();
for (int i = 1; i <= n; i++) fa[i] = i, fa[i + N] = i + N;
build(1, 1, k);
for (int i = 1, l, r; i <= m; i++) {
u[i] = read(), v[i] = read(), l = read(), r = read();
if(l ^ r) ins(1, l + 1, r, i);
}
DFS(1, 1, k);
return 0;
}