[CF1386C] Joker (IOI 赛制,分治,整体二分+可回退并查集)

22 篇文章 0 订阅
20 篇文章 0 订阅

题面

给一个 N N N M M M 边的简单无向图,询问 Q Q Q 次,每次问你把编号在 [ l i , r i ] [l_i,r_i] [li,ri] 之间的边删掉后,该图是否存在奇数环,即是否不能被二染色

1 ≤ N , M , Q ≤ 200000 1\leq N,M,Q\leq 200000 1N,M,Q200000.

题解

看了半天才搞懂官解里的奇怪分治是什么,其实就是整体二分嘛!

部分分就不多赘述了,大概就是一步步引导我们到正解的整体二分+可回退并查集(官解称其为:DSU)上。

并查集是经典的解决二染色问题的方法了,每个点存一个值表示自己和父亲的颜色是否不同(0/1),再存一个集合大小,然后用启发式合并保证每次只变动一条边,最后用一个栈来存每次加的边,以备回退之需。

我们发现答案具有包含性,即:若询问为 [ l , r ] [l,r] [l,r] 时答案是 YES,那么 [ l ′ , r ′ ] [l',r'] [l,r] [ l ′ , r ′ ] ⊂ [ l , r ] [l',r']\sub[l,r] [l,r][l,r])的答案也是 YES ,这个易证,因为能形成奇数环的边都还在。

再换一种思路,我们令 m a x r [ i ] maxr[i] maxr[i]区间左端点 l=i+1满足答案是 YES 的最大右端点编号 +1(即此时 [ 1 , i ] ∪ [ m a x r [ i ] , M ] [1,i]\cup[maxr[i],M] [1,i][maxr[i],M] 以内的边都保留)。那么对于一个询问 [ l , r ] [l,r] [l,r] 当且仅当 m a x r [ l − 1 ] > r maxr[l-1]>r maxr[l1]>r 时答案为 YES。同时,由于之前的包含性,我们可以发现
i < j ⇒ m a x r [ i ] ≤ m a x r [ j ] i<j\Rightarrow maxr[i]\leq maxr[j] i<jmaxr[i]maxr[j]

即: m a x r [ i ] maxr[i] maxr[i] 单调不降!

为了配合可回退并查集,具体地,我们这样分治:

  1. s o l v e ( l 1 , r 1 , l 2 , r 2 ) solve(l_1,r_1,l_2,r_2) solve(l1,r1,l2,r2),四个参数,分别表示两个区间:要求的标号区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1],以及通过之前的计算已经确定的该区间内针对所有位置 m a x r maxr maxr 的范围 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]。为配合并查集,操作前得保证前提条件 l 1 l_1 l1 左边的边和 r 2 r_2 r2 右边的边都已经加入并查集。
  2. 整体二分常规程序,先取 m i d 1 = ⌊ l 1 + r 1 2 ⌋ mid_1=\lfloor\frac{l_1+r_1}{2}\rfloor mid1=2l1+r1,然后把 [ l 1 , m i d 1 ] [l_1,mid_1] [l1,mid1] 的边都加上,再从 r 2 r_2 r2 开始往前加边,直到存在奇数环,即求出了 m a x r [ m i d 1 ] maxr[mid_1] maxr[mid1]
  3. m i d 2 = m a x r [ m i d 1 ] mid_2=maxr[mid_1] mid2=maxr[mid1],那么我们可以往下转移了,我们知道 [ l 1 , m i d 1 − 1 ] [l_1,mid_1-1] [l1,mid11] m a x r maxr maxr 一定在 [ l 2 , m i d 2 ] [l_2,mid_2] [l2,mid2] 以内,以及 [ m i d 1 + 1 , r 1 ] [mid_1+1,r_1] [mid1+1,r1] m a x r maxr maxr 一定在 [ m i d 2 , r 2 ] [mid_2,r_2] [mid2,r2] 以内。
  4. 递归 s o l v e ( m i d 1 + 1 , r 1 , m i d 2 , r 2 ) solve(mid_1+1,r_1,mid_2,r_2) solve(mid1+1,r1,mid2,r2) 。此前暴力回退、加边使之满足前提条件
  5. 递归 s o l v e ( l 1 , m i d 1 − 1 , l 2 , m i d 2 ) solve(l_1,mid_1-1,l_2,mid_2) solve(l1,mid11,l2,mid2)。同理。

递归到边界就不用多说了吧。

时间复杂度 O ( M log ⁡ 2 M + Q ) O(M\log^2M+Q) O(Mlog2M+Q)

CODE

#include<set>
#include<queue>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
int U[MAXN],V[MAXN];
vector<int> g[MAXN];
int fa[MAXN],ds[MAXN],siz[MAXN];
int findd(int x) {if(x == fa[x]) return 0;return findd(fa[x])^ds[x];}
int findf(int x) {return x==fa[x] ? x:findf(fa[x]);}
vector<int> st;
bool unionSet(int a,int b) {
	int u = findf(a),v = findf(b);
	if(siz[u] > siz[v]) swap(u,v),swap(a,b);
	int d1 = findd(a),d2 = findd(b);
	if(u == v) {
		return d1 ^ d2;
	}
	if(d1 == d2) ds[u] = 1;
	else ds[u] = 0;
	siz[v] += siz[u]; fa[u] = v;
	st.push_back(u);
	return 1;
}
void POP() {
	int u = st.back(); st.pop_back();
	int v = fa[u];
	ds[u] = 0; siz[v] -= siz[u];
	fa[u] = u; return ;
}
int maxr[MAXN];
int li[MAXN],ri[MAXN];
void solve(int l,int r,int l2,int r2) {
	if(l > r || l2 > r2) return ;
	if(l2 == r2) {
		for(int i = l;i <= r;i ++) maxr[i] = l2;
		return ;
	}
	int mid = (l + r) >> 1,le = (int)st.size(),rr = max(l2,mid-1);
	int flag = 0;
	for(int i = l;i <= mid;i ++) if(i) flag |= 1^unionSet(U[i],V[i]);
	int le2 = (int)st.size();
	if(flag) rr = r2;
	for(int i = min(r2,m);i > mid && i >= l2 && i > rr;i --) {
		flag |= 1^unionSet(U[i],V[i]);
		if(flag) {rr = i;break;}
	}
	maxr[mid] = rr;
	while((int)st.size() > le2) POP();
	solve(mid+1,r,rr,r2);
	while((int)st.size() > le) POP();
	for(int i = min(r2,m);i >= rr;i --) unionSet(U[i],V[i]);
	solve(l,mid-1,l2,rr);
	while((int)st.size() > le) POP();
	return ;
}
int main() {
	n = read();m = read();int Q = read();
	for(int i = 1;i <= m;i ++) {
		s = read();o = read();
		g[s].push_back(o);
		g[o].push_back(s);
		U[i] = s; V[i] = o;
	}
	int rr = 0;
	for(int i = 1;i <= Q;i ++) li[i] = read(),ri[i] = read(),rr = max(li[i],rr);
	for(int i = 1;i <= n;i ++) fa[i] = i,ds[i] = 0,siz[i] = 1;
	solve(0,m,0,m+1);
	for(int i = 1;i <= Q;i ++) {
		s = li[i];o = ri[i];
		if(maxr[s-1] > o) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值