题目概述
有N个女孩要与N个男孩玩配对游戏.每个女孩有一个可选男孩的集合(即该女孩可以选自己集合中的任意一个男孩作为该轮的搭档).
然后从第一轮开始,每个女孩都要和一个不同的男孩配对.如果第一轮N个女孩都配对成功,那么就开始第二轮配对,女孩依然从自己的备选男孩集合中选择,但是不能选那些已经被该女孩在前几轮选择中选过的男孩了(比如i女孩在第一轮选了j男孩,那么i在第二轮就不能选j男孩了). 问你游戏最多能进行多少轮?
问题分析:
不否认这个题可以用二分图匹配去解决。但这里不做探究。以下为网络流建模解法:
这里如果要使用网络流求解的话,显然我们发现,同一个好友圈(juan)的女生具有相同的性质,即互为好友的女生在这个问题中是等价的,即可以通过一种变换,使得女生A变成女生B~~那么我们通过使用并查集,可以实现将A与B的合并,此时同一并查集中的女生都链接同一组男性。显然这是可行的.
并查集将女生连成整体后,再从女生流到男生(一 一 对 应 所以权值为1)
二分枚举局数(原点到女生的路宽与男生到汇点的路宽)
流入全部流出即可以完成这这样的局数。
INF以介绍过,这里仅提供并查集算法
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
using namespace std;
typedef int captype;//把存储的数据类型在这里定义,避免了大量更改操作
const int MAXN = 210; //点的总数
const int MAXM = 40010; //边的总数
const int INF = 1<<30;
struct EDG{
int to,next;
captype cap,flow;
} edg[MAXM];
int eid,head[MAXN];
int gap[MAXN]; //每种距离(或可认为是高度)点的个数
int dis[MAXN]; //每个点到终点eNode 的最短距离
int cur[MAXN]; //cur[u] 表示从u点出发可流经 cur[u] 号边
int pre[MAXN];
void init(){
eid=0;
memset(head,-1,sizeof(head));
}
//有向边 三个参数,无向边4个参数
void addEdg(int u,int v,captype c,captype rc=0){
edg[eid].to=v; edg[eid].next=head[u];
edg[eid].cap=c; edg[eid].flow=0; head[u]=eid++;
edg[eid].to=u; edg[eid].next=head[v];
edg[eid].cap=rc; edg[eid].flow=0; head[v]=eid++;
}
captype maxFlow_sap(int sNode,int eNode , int n){
memset(gap,0,sizeof(gap));
memset(dis,0,sizeof(dis));
memcpy(cur,head,sizeof(head));
pre[sNode]=-1;
gap[0]=n;
captype ans = 0;
int u=sNode;
while(dis[sNode]<n){
if(u==eNode){
captype mint = INF , mincap = INF;
int minid ;
for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to])
if(mint > edg[i].cap - edg[i].flow){
mint = edg[i].cap - edg[i].flow ;
minid=i;
}
for(int i=pre[u]; i!=-1; i=pre[edg[i^1].to]){
edg[i].flow += mint;
edg[i^1].flow -=mint;
}
ans += mint;
u = edg[minid^1].to;
continue;
}
bool flag=false;
for(int i=cur[u]; i!=-1; i=edg[i].next)
if(edg[i].cap-edg[i].flow>0 && dis[u]==dis[edg[i].to]+1){
cur[u]=pre[edg[i].to]=i;
flag=true;
break;
}
if(flag){
u=edg[cur[u]].to;
continue;
}
int minh=n;
for(int i=head[u]; i!=-1; i=edg[i].next)
if(edg[i].cap-edg[i].flow>0 && minh>dis[edg[i].to]){
minh=dis[edg[i].to];
cur[u]=i;
}
gap[dis[u]]--;
if(gap[dis[u]]==0)
return ans;
dis[u]=minh+1;
gap[dis[u]]++;
if(u!=sNode)
u=edg[pre[u]^1].to;
}
return ans;
}
void changCap(int s,int t,int k){
for(int i=head[s]; i!=-1; i=edg[i].next)
edg[i].cap=k;
for(int i=head[t]; i!=-1; i=edg[i].next)
edg[i^1].cap=k;
for(int i=0; i<eid; i++)//每一次都要清0
edg[i].flow=0;
}
int father[MAXN];
int findfath(int x){
if(x!=father[x])
father[x]=findfath(father[x]);
return father[x];
}
void link(int x,int y){
x=findfath(x);
y=findfath(y);
father[x]=y;
}
int mapt[105][105];
void changMap(int n){
int tmp[105][105]={0};
for(int i=1; i<=n; i++) //对于在同一棵树上可以用一个点来替换(根节点)
father[i]=findfath(i);
for(int i=1; i<=n; i++){
int ti=father[i];
for(int j=1; j<=n; j++)
tmp[ti][j]|=mapt[i][j];
}
for(int i=1; i<=n; i++){
int ti=father[i];
for(int j=1; j<=n; j++)
mapt[i][j]=tmp[ti][j];
}
}
int main(){
int T;
int n,m,f;
int a,b;
scanf("%d",&T);
while(T--){
scanf("%d%d%d",&n,&m,&f);
memset(mapt,0,sizeof(mapt));
while(m--){
scanf("%d%d",&a,&b);
mapt[a][b]=1;
}
for(int i=1; i<=n; i++)
father[i]=i;
while(f--){
scanf("%d%d",&a,&b);
link(a,b);
}
changMap(n);
init();
int s=0 , t=2*n+1;
//女用1~n点表示,男用n+1~n+n表示
for(int i=1; i<=n; i++){
addEdg(s,i,0);
addEdg(i+n,t,0);
for(int j=1; j<=n; j++)
if(mapt[i][j])
addEdg(i,j+n,1);
}
int l=0,r=n ,ans=0;
while(l<=r){
m=(l+r)>>1;
changCap(s,t,m);
int maxflow=maxFlow_sap(s,t,t+1);
if(maxflow==n*m)
ans=m , l=m+1;
else r=m-1;
}
printf("%d\n",ans);
}
}