题意:有n个人,第二行告诉你他是0队还是1队。第三行告诉你是男生还是女生。
现在想要组队,每个队伍必须是一个0队和一个1队的人。
问在组队数尽可能多的情况下,女生的参数人数要最大。
思路:学习KM算法的第一题。带权匹配可以用km算法也可以用费用流解。在km算法中这里的权值我们可以尽可能的设的大一些。例如我设置为10000.一个队伍中每有一个女生权值就++。这样能保证最后在边最多的情况下,女生最多。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=550;
const int inf=0x3f3f3f3f;
int n,nx,ny;
int match[N],lx[N],ly[N],slack[N];
int visx[N],visy[N],w[N][N];
int dfs(int x)
{
visx[x]=1;
for(int y=1;y<=ny;y++){
if(visy[y]) continue;
int tmp=lx[x]+ly[y]-w[x][y];
if(!tmp){
visy[y]=1;
if(match[y]==-1 || dfs(match[y])){
match[y]=x;
return 1;
}
}
else if(slack[y]>tmp) slack[y]=tmp;
}
return 0;
}
int KM()
{
int i,j;
memset(match,-1,sizeof(match));
memset(ly,0,sizeof(ly));
for(i=1;i<=nx;i++)
for(j=1,lx[i]=-inf;j<=ny;j++)
if(w[i][j]>lx[i]) lx[i]=w[i][j];
for(int x=1;x<=nx;x++){
for(i=1;i<=ny;i++)
slack[i]=inf;
while(1){
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfs(x)) break;
int d=inf;
for(int i=1;i<=ny;i++)
if(!visy[i] && d>slack[i]) d=slack[i];
for(int i=1;i<=nx;i++)
if(visx[i]) lx[i]-=d;
for(int i=1;i<=ny;i++)
if(visy[i]) ly[i]+=d;
else slack[i]-=d;
}
}
int ans=0;
for(int i=1;i<=ny;i++)
if(match[i]!=-1)
ans+=w[match[i]][i];
return ans;
}
int vis[N][N];
char group[N],sex[N];
int main(){
//yyy_3y
int T; scanf("%d",&T);
while(T--){
int n;scanf("%d",&n);
memset(vis,0,sizeof(vis));
memset(w,0,sizeof w);
scanf("%s %s",group+1,sex+1);
nx=ny=n;
for(int i=1;i<=n;i++){
int m; scanf("%d",&m);
for(int j=1;j<=m;j++){
int tmp; scanf("%d",&tmp);
vis[i][tmp]=vis[tmp][i]=1;
}
for(int j=1;j<=n;j++){
if(!vis[i][j] && group[i]!=group[j]){
int ret=10000;
if(sex[i]=='0') ret++;
if(sex[j]=='0') ret++;
vis[i][j]=vis[j][i]=1;
if(group[i]=='1') w[i][j]=ret;
else w[j][i]=ret;
}
}
}
int ans=KM();
printf("%d %d\n",ans/10000,ans%10000);
for(int i=1;i<=n;i++){
if(match[i]!=-1 && w[match[i]][i]) printf("%d %d\n",match[i],i);
}
}
return 0;
}
/*
2
3
101
000
0
0
0
3
101
000
0
0
0
*/