1. 程式人生 > 實用技巧 >題解 CF813F 【Bipartite Checking】

題解 CF813F 【Bipartite Checking】

題目連結

Solution CF813F Bipartite Checking

題目大意:給定一個有\(n\)個點,沒有邊的無向圖。每次操作新增一條邊,如果該邊已存在則刪去這條邊。每次操作之後回答無向圖是否為二分圖

擴充套件域 & 可撤銷並查集、線段樹分治


分析:首先如果只有加入操作,我們可以通過擴充套件域並查集來判斷是否可以構成二分圖

如果一個圖是二分圖,等價於可以對圖進行黑白染色使得每條邊的兩端點顏色都不同

那麼我們對於一個端點\(u\),我們可以另開一個點\(u'\)來表示和它顏色不同的點

如果要加邊\((x,y)\),就合併\(x,y'\)\(x',y\)

如果合併後任意\(u\)

\(u'\)在一個集合內此圖都不是二分圖

原題帶撤銷,我們沒辦法快速從並查集上任意刪除一條邊,但是可以\(O(1)\)撤銷最後的一次修改

因此我們可以採用線段樹分治的方法

傳統的線段樹維護序列,這裡維護時間。由於加邊刪邊成對出現(我們認為在時刻\(q + 1\)刪去所有剩餘邊),可以利用線段樹的區間修改方便的加入操作

單點查詢一個時間點,我們可以取得一系列操作,依次執行便可以得到一個時刻的答案

如果暴力將父節點操作推給子節點,複雜度爆炸(沒法\(O(1)pushdown\))。因此我們採用標記永久化的方式。不下傳標記,用vector記錄會影響一個時間段的所有操作,一路走一路累加影響,回溯的時候撤銷

對於統計一條邊的出現時間段,std::map可以做到

#include <cstdio>
#include <cstring>
#include <utility>
#include <map>
#include <stack>
#include <vector>
using namespace std;
const int maxn = 1e5 + 100;
inline int read(){
	int x = 0;char c = getchar();
	while(!isdigit(c))c = getchar();
	while(isdigit(c))x = x * 10 + c - '0',c = getchar();
	return x;
}
struct mpair{int fir,sec;};
int n,q,ans[maxn];
map<int,int> mp[maxn];
namespace mset{
	int f[maxn << 1],siz[maxn << 1];
	inline void init(){
		for(int i = 1;i <= 2 * n;i++)f[i] = i,siz[i] = 1;
	}
	inline int find(int x){while(f[x] != x)x = f[x];return x;}
	inline mpair merge(int x,int y){
		x = find(x),y = find(y);
		if(siz[x] > siz[y])swap(x,y);
		if(x == y)return mpair{-1,-1};
		f[x] = y;
		siz[y] += siz[x];
		return mpair{x,y};
	}
}
namespace seg{
	vector<mpair> vec[maxn << 2];
	#define ls (rt << 1)
	#define rs (rt << 1 | 1)
	inline void modify(int a,int b,mpair v,int l = 1,int r = q,int rt = 1){
		if(a <= l && b >= r){
			vec[rt].push_back(v);
			return;
		}
		int mid = (l + r) >> 1;
		if(a <= mid)modify(a,b,v,l,mid,ls);
		if(b >= mid + 1)modify(a,b,v,mid + 1,r,rs);
	}
	stack<mpair> stk;
	inline void dfs(int rt = 1,int l = 1,int r = q){
		int t = stk.size(),flag = 1;
		for(auto x : vec[rt]){
			mpair res = mset::merge(x.fir,x.sec + n);
			stk.push(res);
			res = mset::merge(x.fir + n,x.sec);
			stk.push(res);
			if(mset::find(x.fir) == mset::find(x.fir + n) || mset::find(x.sec) == mset::find(x.sec + n)){
				flag = 0;
				break;
			}
		}
		if(l == r)ans[l] = flag;
		else if(flag){
			int mid = (l + r) >> 1;
			dfs(ls,l,mid);
			dfs(rs,mid + 1,r);
		}
		while(stk.size() != t){
			int x = stk.top().fir,y = stk.top().sec;
			mset::siz[y] -= mset::siz[x];
			mset::f[x] = x;
			stk.pop();
		}
	}
	#undef ls
	#undef rs
}
int main(){
	n = read(),q = read();mset::init();
	for(int x,y,i = 1;i <= q;i++){
		x = read(),y = read();
		if(mp[x][y])seg::modify(mp[x][y],i - 1,mpair{x,y}),mp[x][y] = 0;
		else mp[x][y] = i;
	}
	for(int i = 1;i <= n;i++)
		for(auto x : mp[i])
			if(x.second)seg::modify(x.second,q,mpair{i,x.first});
	seg::dfs();
	for(int i = 1;i <= q;i++)puts(ans[i] ? "YES" : "NO");
	return 0;
}