题意:
n个男孩n个女孩,女孩选男孩,给出一些女孩喜欢的男孩,并且这些女孩的朋友喜欢的男孩,她也可以喜欢;问这些女孩在不重复挑选男孩过家家的情况下,能挑选几轮(每个女孩不能挑选之前挑选过的男孩),n个女孩都挑好男朋友,算一轮完;
建图 :
源点s=0;汇点t=2*n+1;
1,2,3...n女孩;
n+1,n+2,,,,,,2*n男孩;
假设需要挑x轮,则每个女孩挑了x次男朋友,(每次都一一样)
s到女孩连边,容量为x;
男孩到汇点t连边,容量也为x;
女孩与男孩间连一条容量为1的边,
注意: a女孩的朋友b,若b喜欢w男孩;则a也喜欢w,a与w之间也要连边;
这里用并查集,就可以把互相是朋友集合在一起,改集合里的女孩都有共同喜欢的男孩;
而到底需要挑多少轮,我们可以二分枚举,最多就轮;
#include<cstdio>
#include<cstring>
#include<map>
#include<vector>
#include<cmath>
#include<cstdlib>
#include<stack>
#include<queue>
#include <iomanip>
#include<iostream>
#include<algorithm>
using namespace std ;
const int N=200;
const int M=50000;
struct node1
{
int u,v,c,next;
};
struct node2
{
int x,y;
};
node1 e[M];
node2 ee[M];
int n,m,f,top;
int head[N],cur[N],gap[N],pre[N],dis[N],fa[N];
int mm[N][N];
int find(int x)
{
return x==fa[x] ? x: fa[x]=find(fa[x]) ;
}
void add(int u ,int v ,int c ) //邻接表
{
e[top].u=u;
e[top].v=v;
e[top].c=c;
e[top].next=head[u];
head[u]=top++;
e[top].u=v;
e[top].v=u;
e[top].c=0;
e[top].next=head[v];
head[v]=top++;
}
void build(int t,int mid) //建图
{
top=0;
memset(head,-1,sizeof(head));
memset(mm,0,sizeof(mm));
for(int i = 1 ; i <= n ; i++)
{
add(0,i,mid); //源点与女孩连边
add(n+i,t,mid); //男孩与汇点连边
}
for(int i = 0 ; i < m ;i++)
{
int u=ee[i].x; //女孩u
int v=ee[i].y; //男孩v
for(int j = 1 ; j <= n ;j++) //遍历所有女孩
{
if(find(u)==find(j)) //如果女孩j与女孩u是朋友
if(!mm[j][v]) //并且女孩j和男孩v还没连边
{
mm[j][v]=1;
add(j,n+v,1) ; //则女孩j稀罕男孩v
}
}
}
}
int work(int s,int t ,int nv) //求最大流
{
memset(dis,0,sizeof(dis));
memset(gap,0,sizeof(gap));
memset(pre,-1,sizeof(pre));
int max_flow=0,cur_flow,v,u,neck,i,id,mindis;
for( i = 0 ; i <= t ; i++)
cur[i]=head[i];
gap[0]=nv;
u=s;
while(dis[s]<nv)
{
if(u==t)
{
cur_flow=999999999;
for( i = s ; i!=t ; i = e[cur[i]].v) //从源点沿着增广路扫
{
if(cur_flow > e[cur[i]].c) //找出这条增广路上的最小修改量
{
neck=i; //neck存的是最终修改量的那个点;
cur_flow=e[cur[i]].c ;
}
}
for( i = s ; i!=t ; i = e[cur[i]].v) //增光路上修改流量
{
id=cur[i] ;
e[id].c -= cur_flow ;
e[id^1].c += cur_flow ;
}
u=neck; //修改完,不用回到源点,退到,最小修改量那里,也就是可修改量为0的那点
max_flow += cur_flow ;
}
for( i = cur[u] ; i != -1 ; i = e[i].next) //找u点上的允许弧
{
v=e[i].v;
if(e[i].c>0 && dis[u]==dis[v]+1)
break ;
}
if(i!=-1) //找到允许弧
{
cur[u]=i;
pre[v]=u;
u=v; //继续向后找
}
else //找不到就修改dis[u] ;
{
if(--gap[dis[u]]==0) break; //在u点断层,退出
cur[u]=head[u];
mindis=nv;
for(i = head[u] ; i != -1 ; i = e[i].next)
{
v=e[i].v;
if(e[i].c >0 && dis[v]<mindis) //找最小的dis[v]值
{
mindis=dis[v];
cur[u]=i;
}
}
dis[u]=mindis+1 ; //修改
gap[dis[u]]++; //标记dis[u]层断层,
if(u!=s) u=pre[u]; //退一步,继续
}
}
return max_flow ;
}
int main()
{
int T ;
scanf("%d",&T);
while(T--)
{
memset(ee,0,sizeof(ee));
memset(e,0,sizeof(e));
cin >> n >> m >> f ;
int s=0,t=2*n+1,nv=t+1;
for(int i = 0 ; i < m ; i++)
scanf("%d%d",&ee[i].x,&ee[i].y) ;
for(int i = 0 ; i <= t ; i++)
fa[i]=i ;
for(int i = 0 ; i < f ; i++) //是朋友就合并在一起
{
int u ,v ;
scanf("%d%d",&u,&v);
int x=find(u);
int y=find(v);
if(x!=y)
fa[x]=y;
}
int l = 0 , r = n , ans=0;
while(l<=r) //二分查找要进行多少轮
{
int mid=(l+r)>>1 ;
build(t,mid); //进行mid轮,建图
int res = work(s,t,nv) ; //跑一遍最大流
if(res >= mid*n) //mid轮偏小
{
ans=mid;
l=mid+1 ;
}
else //总流量不够mid轮
r=mid-1;
}
cout << ans << endl ;
}
return 0;
}