题解[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\n",ans);
return 0;
}
ps:打开注释可以看过程的