题解[CF1444C Team-Building]

题目描述

给定一张 (n) 个节点 (m) 条边的无向图,没有自环重边。

每个节点都在一个组中,共有 (k) 组,可能存在某组为空。

求选出两组点,使它们能构成二分图的方案数。

(n,m,k<=5×10^5)

Sol

二分图是图中没有奇数环的图。我们现在每个点集内部找奇环,删掉有奇环的点集。

然后在没有奇环的点集中两两配对,枚举跨越点集的边,如果不合法,答案减一。

这个过程看上去简单,但实现却有点困难。代码巧妙(尤其是连边的方式),码的时候要细心。

上代码。

连边

map<pair<int,int>,int>mp;//映射关系:点集与点集之间的边-->序号
pair<int,int>p[N];//用来存边(点集与点集之间)
vector< pair<int,int> >e[N];//用来存边(点与点之间)
for(int i=1;i<=m;i++){
	int u=read(),v=read();
	if(bl[u]>bl[v]) swap(u,v);
	pair<int,int> nw=make_pair(bl[u],bl[v]);
	if(!mp.count(nw)) mp[nw]=++cnt,p[cnt]=nw;
	e[mp[nw]].push_back(make_pair(u,v));
  //在点集bl[u]与点集bl[v]之间的边,在一个边集e[mp[nw]]中
}

找点集内部奇数环

for(int i=1;i<=k;i++){
	pair<int,int> now=make_pair(i,i);
  //看第i个点集内部有没有边
	if(!mp.count(now)) continue;
	for(int j=0;j<(int)e[mp[now]].size();j++){
		int x=e[mp[now]][j].first,y=e[mp[now]][j].second;
      //枚举边
		int nx=mfind1(x),ny=mfind1(y);
      //有奇环
		if(nx==ny&&(d1[x]^d1[y]^1)){bad[i]=1;continue;}
      //x是要成为y的儿子的,如果x到y的祖先的路上(这是一个返祖过程)要经过奇数条边,就说明存在奇环
		d1[nx]=d1[x]^d1[y]^1,f1[nx]=ny;
      //合并,更新距离
	}
}

点集之间的不合法状态

for(int i=1;i<=cnt;i++){
	int xx=p[i].first,yy=p[i].second;
	if(xx==yy||bad[xx]||bad[yy]) continue;
  //自己到自己||奇环点集-->跳过
	for(int j=0;j<(int)e[i].size();j++){			
		int x=e[i][j].first,y=e[i][j].second;
		int nx=mfind1(x),ny=mfind1(y);
		d2[nx]=0,d2[ny]=0,f2[nx]=nx,f2[ny]=ny;
      //初始化
	}
	for(int j=0;j<(int)e[i].size();j++){
		int x=e[i][j].first,y=e[i][j].second;
		int nx=mfind1(x),ny=mfind1(y);
		int fx=mfind2(nx),fy=mfind2(ny);
		if(fx==fy&&(d2[nx]^d1[x]^d2[ny]^d1[y]^1)){ans--;break;}
		d2[fx]=(d2[nx]^d1[x]^d2[ny]^d1[y]^1),f2[fx]=fy;
      //同理,判断奇环
	}
}

注意

(d1,f1)为第一个带权并查集

(d2,f2)是临时的,合并两个点集的并查集,每次都要更新

Code

#include<bits/stdc++.h>
#define N (500010)
#define ll long long
using namespace std;
int n,m,k,cnt,tot;
int bl[N],f1[N],f2[N];
bool d1[N],d2[N],bad[N];
map<pair<int,int>,int>mp;
pair<int,int>p[N];
vector< pair<int,int> >e[N];
inline int read(){
	int w=0;
	char ch=getchar();
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch>='0'&&ch<='9'){
		w=(w<<3)+(w<<1)+(ch^48);
		ch=getchar();
	}
	return w;
}
inline int mfind1(int x){
	if(x!=f1[x]){
		int rt=mfind1(f1[x]);
		d1[x]^=d1[f1[x]];
		return f1[x]=rt;
	}
	return x;
}
inline int mfind2(int x){
	if(f2[x]!=x){
		int rt=mfind2(f2[x]);
		d2[x]^=d2[f2[x]];
		return f2[x]=rt;
	}
	return x;
}
int main(){
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++) bl[i]=read(),f1[i]=i,d1[i]=0;
	for(int i=1;i<=m;i++){
		int u=read(),v=read();
		if(bl[u]>bl[v]) swap(u,v);
		pair<int,int> nw=make_pair(bl[u],bl[v]);
		if(!mp.count(nw)) mp[nw]=++cnt,p[cnt]=nw;
		e[mp[nw]].push_back(make_pair(u,v));
	}
	for(int i=1;i<=k;i++){
		pair<int,int> now=make_pair(i,i);
		if(!mp.count(now)) continue;
		for(int j=0;j<(int)e[mp[now]].size();j++){
			int x=e[mp[now]][j].first,y=e[mp[now]][j].second;
			int nx=mfind1(x),ny=mfind1(y);
			if(nx==ny&&(d1[x]^d1[y]^1)){bad[i]=1;continue;}
			d1[nx]=d1[x]^d1[y]^1,f1[nx]=ny;
		}
	}
//	for(int i=1;i<=n;i++) cout<<f1[i]<<" ";
//	puts("");
	for(int i=1;i<=k;i++)
		if(!bad[i]) tot++;
//	cout<<"tot="<<tot<<endl;
//	cout<<"cnt="<<cnt<<endl;
//	for(int i=1;i<=n;i++) cout<<d2[i]<<" ";
//	puts("");
	ll ans=(ll)tot*(tot-1)/2;
	for(int i=1;i<=cnt;i++){
		int xx=p[i].first,yy=p[i].second;
		if(xx==yy||bad[xx]||bad[yy]) continue;
		for(int j=0;j<(int)e[i].size();j++){			
			int x=e[i][j].first,y=e[i][j].second;
			int nx=mfind1(x),ny=mfind1(y);
			d2[nx]=0,d2[ny]=0,f2[nx]=nx,f2[ny]=ny;
		}
//		for(int j=1;j<=n;j++) cout<<d2[j]<<" ";
//		puts("");
		for(int j=0;j<(int)e[i].size();j++){
			int x=e[i][j].first,y=e[i][j].second;
			int nx=mfind1(x),ny=mfind1(y);
			int fx=mfind2(nx),fy=mfind2(ny);
			if(fx==fy&&(d2[nx]^d1[x]^d2[ny]^d1[y]^1)){ans--;break;}
			d2[fx]=(d2[nx]^d1[x]^d2[ny]^d1[y]^1),f2[fx]=fy;
		}
//		for(int j=1;j<=n;j++) cout<<d2[j]<<" ";
//		puts("");
//		cout<<"ans="<<ans<<endl;
	}
	printf("%lld
",ans);
	return 0;
}

ps:打开注释可以看过程的

完结撒花❀

原文地址:https://www.cnblogs.com/xxbbkk/p/14453264.html