- 给定一张\(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;
}