Bipartite Checking[CF813F][线段树分治][带权并查集]

文章目录

  • 题目
  • 思路
  • 代码

题目

Luogu
在这里插入图片描述
2 ≤ n , q ≤ 1 0 5 2\le n,q\le 10^5 2n,q105

思路

通过带权并查集判断二分图真是妙(以前没见过)
Bipartite Checking[CF813F][线段树分治][带权并查集]_第1张图片
首先我们能找到每条边的出现时间 [ l i , r i ] [l_i,r_i] [li,ri] ,那么线段树分治后
发现是一个区间修改,单点查询的样子,修改标记永久化即可
然后就只剩下如何处理加边和删边的问题了
然后发现好像网上都把判断二分图当作众所周知…

我们使用带权并查集来解决这个问题,具体而言定义 f a [ u ] = u fa[u]=u fa[u]=u 的节点颜色为 0 0 0 ,然后每个点上有一个标记 c u c_u cu 表示和 f a [ u ] fa[u] fa[u] 的颜色异同关系,显然一个点的颜色就是它到根的 c u c_u cu 异或和
可以用线段树懒标记类比
一次 ( u , v ) (u,v) (u,v) 的加边操作如何实现?
如果 ( u , v ) (u,v) (u,v) 不连通,我们首先得到 u , v u,v u,v 各自颜色,如果不同那么并查集合并表示连通性,颜色相同将其中一个并查集顶端颜色变换连接即可
如果 ( u , v ) (u,v) (u,v) 连通,那么两个点如果同色就不合法
然后删除操作我们用回撤即可
相关代码

void Merge(int u,int v,int flag){
	u=Find(u),v=Find(v);
	if(u==v) return ;
	if(dep[u]<dep[v]) swap(u,v);
	else if(dep[u]==dep[v])
		dep[u]++,Stk[++tp]=-u;
	fa[v]=u,c[v]=flag,Stk[++tp]=v;
	return ;
}
void Restore(int ntp){
	while(tp>ntp){
		if(Stk[tp]<0) dep[-Stk[tp]]--;
		else fa[Stk[tp]]=Stk[tp],c[Stk[tp]]=0;
		tp--;
	}
	return ;
}

其实用并查集拓展域也能实现,只不过不怎么流行…
注意这里线段树分治是统一处理询问优化的时间复杂度

代码

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define LL long long
//#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
//char buf[(1 << 21) + 1], *p1 = buf, *p2 = buf;
inline LL read() {
	bool f=0;LL x=0;char c=getchar();
	while(c<'0'||'9'<c){if(c==EOF)exit(0);if(c=='-')f=1;c=getchar();}
	while('0'<=c&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return !f?x:-x;
}
#define MAXN 100000
#define INF 100000000000000ll
int Stk[MAXN+5],tp;
int fa[MAXN+5],c[MAXN+5],dep[MAXN+5];
int Find(int u){return fa[u]==u?u:Find(fa[u]);}
int Dis(int u){return fa[u]==u?0:(Dis(fa[u])^c[u]);}
void Merge(int u,int v,int flag){
	u=Find(u),v=Find(v);
	if(u==v) return ;
	if(dep[u]<dep[v]) swap(u,v);
	else if(dep[u]==dep[v])
		dep[u]++,Stk[++tp]=-u;
	fa[v]=u,c[v]=flag,Stk[++tp]=v;
	return ;
}
void Restore(int ntp){
	while(tp>ntp){
		if(Stk[tp]<0) dep[-Stk[tp]]--;
		else fa[Stk[tp]]=Stk[tp],c[Stk[tp]]=0;
		tp--;
	}
	return ;
}
#define lch (rt<<1)
#define rch (rt<<1|1)
#define mp make_pair
#define pii pair
vector<pii >tree[5*MAXN+5];
void Insert(int rt,int L,int R,int qL,int qR,pii x){
	if(qL<=L&&R<=qR){
		tree[rt].push_back(x);
		return ;
	}
	int Mid=(L+R)>>1;
	if(qL<=Mid)
		Insert(lch,L,Mid,qL,qR,x);
	if(Mid+1<=qR)
		Insert(rch,Mid+1,R,qL,qR,x);
	return ;
}
void DFS(int rt,int L,int R){
	int ntp=tp;
	for(int i=0;i<(int)tree[rt].size();i++){
		int u=tree[rt][i].first,v=tree[rt][i].second;
		int flag=Dis(u)^Dis(v)^1;//同色变色
		if(Find(u)==Find(v)){
			if(flag){
				for(int j=L;j<=R;j++)
					puts("NO");
				Restore(ntp);
				return ;
			}
		}
		else Merge(u,v,flag);
	}
	if(L==R){
		puts("YES");
		Restore(ntp);
		return ;
	}
	int Mid=(L+R)>>1;
	DFS(lch,L,Mid),DFS(rch,Mid+1,R);
	Restore(ntp);
	return ;
}
map<pii,int> Map;
int main(){
	int n=read(),q=read();
	for(int i=1;i<=n;i++)
		fa[i]=i;
	for(int i=1;i<=q;i++){
		int u=read(),v=read();
		if(Map.count(mp(u,v)))
			Insert(1,1,q,Map[mp(u,v)],i-1,mp(u,v)),Map.erase(mp(u,v));
		else Map[mp(u,v)]=i;
	}
	for(map<pii,int>::iterator it=Map.begin();it!=Map.end();it++)
		Insert(1,1,q,it->second,q,it->first);
	DFS(1,1,q);
	return 0;
}

你可能感兴趣的:(并查集,线段树分治)