题干:
Presumably, you all have known the question of stable marriage match. A girl will choose a boy; it is similar as the game of playing house we used to play when we are kids. What a happy time as so many friends playing together. And it is normal that a fight or a quarrel breaks out, but we will still play together after that, because we are kids.
Now, there are 2n kids, n boys numbered from 1 to n, and n girls numbered from 1 to n. you know, ladies first. So, every girl can choose a boy first, with whom she has not quarreled, to make up a family. Besides, the girl X can also choose boy Z to be her boyfriend when her friend, girl Y has not quarreled with him. Furthermore, the friendship is mutual, which means a and c are friends provided that a and b are friends and b and c are friend.
Once every girl finds their boyfriends they will start a new round of this game—marriage match. At the end of each round, every girl will start to find a new boyfriend, who she has not chosen before. So the game goes on and on.
Now, here is the question for you, how many rounds can these 2n kids totally play this game?
Input
There are several test cases. First is a integer T, means the number of test cases.
Each test case starts with three integer n, m and f in a line (3<=n<=100,0<m<n*n,0<=f<n). n means there are 2*n children, n girls(number from 1 to n) and n boys(number from 1 to n).
Then m lines follow. Each line contains two numbers a and b, means girl a and boy b had never quarreled with each other.
Then f lines follow. Each line contains two numbers c and d, means girl c and girl d are good friends.
Output
For each case, output a number in one line. The maximal number of Marriage Match the children can play.
Sample Input
1
4 5 2
1 1
2 3
3 2
4 2
4 4
1 4
2 3
Sample Output
2
题目大意:
n个女生与n个男生配对,每个女生只能配对某些男生,有些女生相互是朋友,每个女生也可以跟她朋友能配对的男生配对。
每次配对,每个女生都要跟不同的男生配对且每个女生都能配到对。问最多能配对几轮。
解题报告:(贴一个题解链接)
考虑用网络流做,首先女生可以与她朋友能配对的男生配对,这样需要用并查集保存他们可以配对的关系。
为了保证每一轮所有女生和男生都能匹配到,我们需要二分源点和女生、男生和汇点之间的容量k,并且需要保证满流。找到最大的满足条件的k就是答案。
解法的正确性可以用数学归纳法证明,当k=1时,转化为一个二分匹配,如果满流,就说明可以进行一轮。当k-1轮可以实现时(k-1满流),如果容量为k时满流,说明也可以实现k轮。这样就证明了正确性。
由这个解法我们也可以想到用二分匹配的方法来解决:进行二分图的最大匹配,在匹配完成后判断匹配数是否等于n,不是的话说明GAME OVER 求得答案,是的话说明游戏能完成,然后进行删边操作,再继续匹配,直到匹配数<n则得到答案。
AC代码:
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<set>
using namespace std;
const int INF = 0x3f3f3f3f;
int tot;
struct Edge {int to,ne,w;
} e[1000005 * 2];
int head[10005];
int st,ed;
int dis[10050],q[10005];//一共多少个点跑bfs,dis数组和q数组就开多大。
void add(int u,int v,int w) {e[++tot].to=v; e[tot].w=w; e[tot].ne=head[u]; head[u]=tot;e[++tot].to=u; e[tot].w=0; e[tot].ne=head[v]; head[v]=tot;
}
bool bfs(int st,int ed) {memset(dis,-1,sizeof(dis));int front=0,tail=0;q[tail++]=st;dis[st]=0;while(front<tail) {int cur = q[front];if(cur == ed) return 1;front++;for(int i = head[cur]; i!=-1; i = e[i].ne) {if(e[i].w&&dis[e[i].to]<0) {q[tail++]=e[i].to;dis[e[i].to]=dis[cur]+1;}}}if(dis[ed]==-1) return 0;return 1;
}
int dfs(int cur,int limit) {//limit为源点到这个点的路径上的最小边权 if(limit==0||cur==ed) return limit;int w,flow=0;for(int i = head[cur]; i!=-1; i = e[i].ne) { if(e[i].w&&dis[e[i].to]==dis[cur]+1) {w=dfs(e[i].to,min(limit,e[i].w));e[i].w-=w;e[i^1].w+=w;flow+=w;limit-=w;if(limit==0) break;}}if(!flow) dis[cur]=-1;return flow;
}
int dinic() {int ans = 0;while(bfs(st,ed)) ans+=dfs(st,0x7fffffff);return ans;
}
int v[100005],u[100005],n,m,f;
int fa[100005];
bool has[555][555];
int getf(int v) {return fa[v] == v ? v : fa[v] = getf(fa[v]);
}
bool ok(int x) {//每次都要重新建图 tot=1;memset(head,-1,sizeof head);for(int i = 1; i<=n; i++) add(st,i,x);for(int i = 1; i<=n; i++) add(n+i,ed,x);//设为INF也可以??答:并不可以
// for(int i = 1; i<=m; i++) add(u[i],n+v[i],1);for(int i = 1; i<=n; i++) {for(int j = 1; j<=n; j++) if(has[i][j]) add(i,j+n,1);}int ans = dinic();if(x*n == ans) return 1;else return 0;
}
int main()
{int t;cin>>t;while(t--) {scanf("%d%d%d",&n,&m,&f);memset(has,0,sizeof has);for(int i = 1; i<=n; i++) fa[i]=i;st=2*n+1,ed=st+1;for(int i = 1; i<=m; i++) {//u女v男 scanf("%d%d",u+i,v+i);has[u[i]][v[i]]=1;}for(int u,v,i = 1; i<=f; i++) {//f对女生 scanf("%d%d",&u,&v);u=getf(u),v=getf(v);fa[u]=v;}for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)if(getf(i)==getf(j))for(int k=1;k<=n;++k)if(has[i][k])has[j][k]=1;int l = 0,r = n,mid,ans=0;while(l<=r) {mid=(l+r)>>1;if(ok(mid)) l=mid+1,ans=mid;else r = mid-1;}printf("%d\n",ans); }return 0;
}
总结:
错误想法1:S-Girl和Boy-T设成INF,然后跑一次最大流,然后答案为S-Girl和Boy-T的边中流量的最小值,为什么不行。
给一组样例:
1
3 4 0
1 2
2 1
2 3
3 2
答案应该是0,但用上述方法答案为1。
错误想法2:
考虑到n个女生和n个男生不重复完全配对最多只能进行n轮,想到可以将源点和女生、男生和汇点之间的容量赋为n,求出最大流。但是发现这样是不行的,如果有女生或者男生一直无法配对,则答案本应是0,但是最大流肯定是>0的,所以答案是不对的。这个想法错就错在没有抓住那个关键点:每一次舞会一定是男生女生全员参与,也就是抽象成:女生这边都流出1的流量总有所有的男生全部接受到,也就是男生这边接收到的流量总是等量的,不能是:1号男生接受到2流量,2号男生没有流量流过。
错误想法3:
为什么不能直接把男生和汇点的流量设置为INF?
答:这也是和错误想法2差不多相同的错误。你这样没法保证每个男生接受的流量总是等量的。
本来想着是把左侧的点再拆成n个点,然后对应连到右边,后来发现没啥意义,可以直接连到右边的。
不过本来还有个想法,那就是把她和她的朋友关系直接转化到左边的点(女生之间的点)相连,而不直接转化到和男生的对应关系,但是后来发现这样是不行的,因为你这样没法控制每一轮,每个女生只能和一个男生建立关系,因为你这样相当于把她俩女生当成一个完全一致的物品去看待了,但是其实她俩肯定是有区别的。