线段树分治学习笔记
比赛又双叒叕遇到新科技了(
线段树分治,是一种基于时间的分治思想(类比 CDQ 分治)。
利用线段树将某个区间至多分成 \(\log\) 段的思想,可以方便得处理一些便于插入和撤销的操作。
例如板子题。
判断二分图可以利用扩展域并查集,而且利用可撤销并查集(仅按秩合并 + 栈记录)可以做到单次 \(O(\log n)\)。
但是关键是边的消失和出现不太好处理,可以利用线段树的结构,将操作挂在代表区间的节点上,统一处理。
这样就完美 \(O(n\log n\log k)\) 解决了,其中 \(k\) 是时间维。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef pair<int, int> PII;
#define MP make_pair
#define U first
#define V second
const int N = 1e5 + 10;
int n, m, k, tot, fa[N << 1], h[N << 1];
PII stk[N];
vector<PII> G[N << 2];
int read(){
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void Modify(int p, int l, int r, int L, int R, int u, int v){
if(L <= l && r <= R) {G[p].push_back(MP(u, v)); return;}
int mid = (l + r) >> 1;
if(L <= mid) Modify(p << 1, l, mid, L, R, u, v);
if(R > mid) Modify(p << 1 | 1, mid + 1, r, L, R, u, v);
}
int Get(int x){
if(x == fa[x]) return x;
return Get(fa[x]);
}
void Merge(int u, int v){
int fu = Get(u), fv = Get(v);
if(fu == fv) return;
if(h[fu] > h[fv]) swap(fu, fv);
stk[++ tot] = MP(fu, h[fu] == h[fv]);
fa[fu] = fv, h[fv] += (h[fu] == h[fv]);
}
void Work(int p, int l, int r){
bool flag = true;
int las = tot;
for(int i = 0; i < (int) G[p].size(); i ++){
int u = G[p][i].U;
int v = G[p][i].V;
if(Get(u) == Get(v)) {flag = false; break;}
Merge(u, v + n);
Merge(u + n, v);
}
if(flag){
if(l == r) puts("Yes");
else{
int mid = (l + r) >> 1;
Work(p << 1, l, mid);
Work(p << 1 | 1, mid + 1, r);
}
}
else
for(int i = l; i <= r; i ++) puts("No");
while(tot != las){
int u = stk[tot].U, v = stk[tot].V;
h[fa[u]] -= v; fa[u] = u; tot --;
}
}
int main(){
n = read(), m = read(), k = read();
for(int i = 1; i <= m; i ++){
int u = read() , v = read();
int l = read() + 1, r = read();
Modify(1, 1, k, l, r, u, v);
}
for(int i = 1; i <= (n << 1); i ++) fa[i] = i, h[i] = 1;
Work(1, 1, k);
return 0;
}