一道非常神仙的题 %%%%%%%%%%%%
首先看到这样的设问,做题数量多一点的同学不难想到这个题。事实上对于此题而言,题面中那个“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
*/