Codeforces 1499G

Codeforces 题面传送门 & 洛谷题面传送门

一道非常神仙的题 %%%%%%%%%%%%

首先看到这样的设问,做题数量多一点的同学不难想到这个题。事实上对于此题而言,题面中那个“Classical and Easy”的问题就是那题的弱化版,具体来说,借鉴那题的思路,我们考虑建立一个虚点 (V)​,然后对于所有度为奇数的点 (x),我们连一条 (x)(V) 之间的双向边,然后跑欧拉回路。对于每一条原二分图中的边,假设其左部点为 (x),右部点为 (y),那么如果边 ((x,y)) 在欧拉回路上的方向为 (x o y) 那么我们给这条边染蓝,否则我们给这条边染红,不难发现这样每个点的 (|r(x)-b(x)|) 之和达到了理论下界,也就是 (sumlimits_{x}[deg_x ext{ is odd}])

然后我就企图直接对着这个版本解决此题,想着怎么用 set 维护每一个环,怎么启发式合并,然后发现复杂度爆炸,心态也随之爆炸……

事实上,对于此题多组询问的版本而言,如果直接这么做那么会牵扯到虚点加边删边的问题,会导致问题变得异常麻烦。因此考虑怎样不建虚点来解决这个问题,显然对于原来包含虚点的图而言,如果我们在遍历包含虚点的连通块遍历到了一个包含虚点的环 (V o v_1 o v_2 ocdots o v_k o V)​,那么如果我们把 (V)​ 删掉,那就会得到一个欧拉路径,因此如果我们不建虚点,那么问题即可以转化为,要找到 (C=sumlimits_{x}[deg_x ext{ is odd}])​ 个欧拉路径与一些环,并将这些路径与环上的边定向。

考虑怎么维护这些环,不难发现我们加入一条边时,如果存在某两条路径分别以这条边的两个端点为端点,那么我们就要将这两条路径并起来。看到这个“并”,我们可以很自然地想到并查集维护每条路径中边的集合。不过注意到这里涉及到路径的定向问题,因此我们不能使用普通的并查集——我们需要带权并查集。具体来说,对于一条边我们记其为 (0) 表示这条边方向是由左部点连到右部点,(1) 则反过来。那么当我们加入一条边 ((x,y)) 时:

  • 如果 (x,y) 都不是某条边的端点,那么我们就令新加入的这条边单独成一个集合,权值 (0/1) 皆可。
  • 如果 (x,y) 中恰好有一个是某条边的端点,不妨设 (x) 是某条边的端点,如果与 (x) 相连的路径上的边的权值为 (1),那么令新加入的这条边的权值为 (0),否则令新加入的边的权值为 (1),然后将两个集合并起来即可。
  • 如果 (x,y) 都是某条边的端点,这种 case 稍微有点麻烦。如果与 (x,y) 直接相连的两条边的权值相同,那么我们就令 ((x,y)) 的权值为与 (x) 相连的边的权值异或一,然后将三个集合并起来。否则我们就将 (y) 所在路径中所有边的权值 flip 一下,然后还是令 ((x,y)) 权值为与 (x) 相连的边的权值异或一,将三个集合并起来即可。

u1s1 在做这道题之前我甚至还不会带权并查集(主要是当时 2 years ago 没认真学,大概老师讲这东西的时候我在打游戏),主要思想大概就对于一个并查集,我们定义 (x) 点的权值为 (w_x),那么我们不维护 (w_x),instead 我们维护一个 (w’_x),满足 (w_x) 等于 (x) 到根节点路径上所有点的 (w’) 的和(或异或和、或 (min),取决于你定义了啥运算),那么在路径压缩的时候,我们考虑先遍历其父亲 (f),那么在遍历完父亲之后,父亲的 (w’) 值就是父亲真正的的 (w) 值减去根节点的 (w) 值,那么我们就直接令 (x) 的父亲为根节点,然后令 (x)(w’) 值为父亲此时的 (w’) 值加上 (x) 原来的 (w’) 值即可,这样查询一个点真正的 (w) 值就可以拿 (x) 在路径压缩后的 (w’) 与根节点的 (w) 相加,将一个连通块中所有点的权值加上(或异或上)某个数 (x) 就直接令该连通块根节点的权值加上(或异或上)(x) 即可,这个异常好懂(

时间复杂度 (mathcal O((n+q)log n))

const int MAXN=4e5;
const int MOD=998244353;
int n1,n2,m,mm,pw2[MAXN+5],tag[MAXN+5],f[MAXN+5],res=0,sum[MAXN+5][2];
int find(int x){
	if(!f[x]) return x;if(!f[f[x]]) return f[x];
	int fx=find(f[x]);tag[x]^=tag[f[x]];return f[x]=fx;
}
bool ask(int x){if(!f[x]) return tag[x];int fx=find(x);/*printf("%d %d
",tag[fx],tag[x]);*/return tag[fx]^tag[x];}
void rev(int x){
//	printf("reverse %d
",x);
	x=find(x);res=(res-sum[x][tag[x]]+MOD)%MOD;
	tag[x]^=1;res=(res+sum[x][tag[x]])%MOD;
}
void merge(int x,int y){
	x=find(x);y=find(y);if(x==y) return;f[x]=y;
//	printf("merge %d %d
",x,y);
	res=(res-sum[x][tag[x]]+MOD)%MOD;res=(res-sum[y][tag[y]]+MOD)%MOD;
	sum[y][tag[y]]=(sum[y][tag[y]]+sum[x][tag[x]])%MOD;
	sum[y][tag[y]^1]=(sum[y][tag[y]^1]+sum[x][tag[x]^1])%MOD;
	tag[x]^=tag[y];res=(res+sum[y][tag[y]])%MOD;
}
int con[MAXN+5];
void dealins(int x,int y){
	int id=++mm;sum[id][0]=pw2[id];res=(res+pw2[id])%MOD;
	if(!con[x]&&!con[y]) return con[x]=con[y]=id,void();
//	printf("ins %d %d
",x,y);
	if(!con[x]) swap(x,y);
	if(!con[y]){
//		printf("%d %d
",x,y);
		if(!ask(con[x])) rev(id);//printf("%d
",res);
		merge(id,con[x]);con[x]=0;con[y]=id;
	} else {
		if(ask(con[x])!=ask(con[y])) rev(con[y]);
		if(!ask(con[x])) rev(id);
		merge(id,con[x]);merge(id,con[y]);con[x]=con[y]=0;
	}
}
void prt_color(){
	int cnt=0;
	for(int i=1;i<=mm;i++) if(!ask(i)) cnt++;
	printf("%d
",cnt);
	for(int i=1;i<=mm;i++) if(!ask(i)) printf("%d ",i);
	printf("
");
}
int main(){
	scanf("%d%d%d",&n1,&n2,&m);
	pw2[0]=1;for(int i=1;i<=MAXN;i++) pw2[i]=(pw2[i-1]<<1)%MOD;
	for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),dealins(u,v+n1);
	int qu;scanf("%d",&qu);
	while(qu--){
		int opt;scanf("%d",&opt);
		if(opt==1){
			int u,v;scanf("%d%d",&u,&v);
			dealins(u,v+n1);printf("%d
",res);
		} else prt_color();
		fflush(stdout);
	}
	return 0;
}
/*
2 2 2
1 1
2 2
2
1 1 2
2
*/
原文地址:https://www.cnblogs.com/ET2006/p/Codeforces-1499G.html