【CF576E】Painting Edges(线段树分治+并查集)

点此看题面

  • 给定一张\(n\)个点\(m\)条边的图,有\(k\)种颜色。
  • \(q\)次操作,每次修改一条边的颜色,如果修改之后这种颜色的边会形成奇环就不执行该操作。
  • 问每次操作是否会执行。
  • \(n,m,q\le5\times10^5,k\le50\)

线段树分治

刚看完题面以为是道大裸题,仔细一想突然发现因为一些操作有可能不执行,无法直接确定出每条边被删除的时间。

但实际上,一次操作如果不执行,我们可以看作它给这条边染上了与原先相同的颜色。

然后我们又发现线段树分治有一个很好的性质,就是我们肯定会先访问到一个操作对应的叶节点,然后才会处理到这个操作的染色修改。

即,我们每次都可以先判断这个操作是否会执行,这样就能确定它到底染了什么颜色。

于是发现这还是一道大裸题。。。

按秩合并并查集判奇环

还有一个很简单的问题,就是按秩合并并查集怎么判奇环。

设\(V(x)\)表示点\(x\)到它所在连通块的根的路径长度的奇偶性。

比如说当前要在\(x,y\)之间连边,它们所在连通块的根分别是\(fx,fy\)。

那么\(y\)所在连通块的所有点,到新的根节点\(fx\)路径长度的奇偶性都会异或上\(V(x)\ xor\ V(y)\ xor\ 1\)。

那么只要对于并查集每个点打一个标记,然后一个点的\(V\)就是它到根所有标记的异或和。

代码:\(O(nlog^2n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define K 50
using namespace std;
int n,m,k,Qt,lst[N+5];struct line {int x,y;}s[N+5];struct Q {int x,c;}q[N+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
class UnionFindSet//按秩合并并查集
{
	private:
		int f[N+5],g[N+5],T,Sx[N+5],Sy[N+5];bool tg[N+5],Sg[N+5];
	public:
		I int fa(CI x) {return f[x]?fa(f[x]):x;}
		I int V(CI x) {return (f[x]?V(f[x]):0)^tg[x];}//当前点到根所有标记异或和
		I void U(RI x,RI y)//合并
		{
			RI fx=fa(x),fy=fa(y);if(++T,fx==fy) return (void)(Sx[T]=-1);
			g[fx]<g[fy]&&(swap(x,y),swap(fx,fy),0),Sx[T]=fx,Sy[T]=fy,
			g[fx]+=(Sg[T]=g[fx]==g[fy]),f[fy]=fx,tg[fy]=V(x)^V(y)^1;
		}
		I void Back() {~Sx[T]&&(g[Sx[T]]-=Sg[T],f[Sy[T]]=tg[Sy[T]]=0),--T;}//撤销
}U[K+5];
class SegmentTreeSolver//线段树分治
{
	private:
		#define PT CI l=1,CI r=Qt,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		int o[N+5];vector<int> P[N<<2];vector<int>::iterator it;
	public:
		I void T(CI L,CI R,CI p,PT)//给区间[L,R]加入操作p
		{
			if(L<=l&&r<=R) return P[rt].push_back(p);RI mid=l+r>>1;
			L<=mid&&(T(L,R,p,LT),0),R>mid&&(T(L,R,p,RT),0);
		}
		I void Solve(PT)//线段树分治
		{
			for(it=P[rt].begin();it!=P[rt].end();++it) U[q[*it].c].U(s[q[*it].x].x,s[q[*it].x].y);//执行操作
			RI p,x,y,c;if(l==r) x=s[p=q[l].x].x,y=s[p].y,c=q[l].c,//对于叶节点
				puts(U[c].fa(x)^U[c].fa(y)||U[c].V(x)^U[c].V(y)?(o[p]=c,"YES"):(q[l].c=o[p],"NO"));//判断操作是否要执行
			else {RI mid=l+r>>1;Solve(LT),Solve(RT);}//对于非叶节点直接递归
			for(it=P[rt].begin();it!=P[rt].end();++it) U[q[*it].c].Back();//撤销操作
		}
}S;
int main()
{
	RI i;for(F.read(n,m,k,Qt),i=1;i<=m;++i) F.read(s[i].x,s[i].y);
	for(i=1;i<=Qt;++i) F.read(q[i].x,q[i].c),
		lst[q[i].x]&&(S.T(lst[q[i].x]+1,i,lst[q[i].x]),0),lst[q[i].x]=i;//求出每一个操作对应范围
	for(i=1;i<=m;++i) lst[i]&&(S.T(lst[i]+1,Qt,lst[i]),0);return S.Solve(),0;
}
上一篇:【BZOJ3730】震波(点分树)


下一篇:Oh Those Rollers(典型BFS)