HDU3081:Marriage Match II (Floyd/并查集+二分图匹配/最大流(+二分))

Marriage Match II

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5469    Accepted Submission(s): 1756

题目链接http://acm.hdu.edu.cn/showproblem.php?pid=3081

Description:

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个男生,每个女生都有一个选择男生的集合。其中有些女生是朋友关系,朋友之间可以选择彼此男生的集合 /斜眼笑。

现在进行游戏,每个女生都选择一名之前没有选择过的男生,当所有男女成功配对后就分手然后开始下一轮游戏= =

这里都是女生选择男生,问最多能成功进行几轮游戏。

 

题解:

先说说用网络流的解法。

首先用并查集或者floyd算法解决朋友之间的问题(女生可以选择其朋友的男生集合)。

然后考虑女生向其能够选择的男生连容量为1 的边,源点向女生连容量为1的边,男生向汇点连容量为1的边。

之后不断地跑网络流然后删边就行了。

但是这样做有点麻烦。我们会发现,每次删边后流从其他边走,等价于在这个点的入流和出流可以为2。

然后就可以这样想:二分回合数,与源点和汇点相连的边的容量为二分值,然后直接跑最大流就行了。

如果max_flow=n*mid,说明回合数可以调大点,否则就调小点。

 

下面是用并查集的网络流:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#include <set>
#define INF 1e9
#define t 300
using namespace std;
typedef long long ll;
const int N = 305;
int T,cnt;
int n,m,F,tot;
int head[N],f[N],map[N],d[N];
vector <int > g[N];
vector <int > vec[N];
set <int > S[N];
struct Edge{
    int v,next,c;
}e[(N*N)<<1];
void adde(int u,int v,int c){
    e[tot].v=v;e[tot].next=head[u];e[tot].c=c;head[u]=tot++;
    e[tot].v=u;e[tot].c=0;e[tot].next=head[v];head[v]=tot++;
}
int find(int x){
    return f[x]==x ? f[x] : f[x]=find(f[x]);
}
int bfs(){
    memset(d,0,sizeof(d));d[0]=1;
    queue <int > q;q.push(0);
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=e[i].next){
            int v=e[i].v;
            if(e[i].c>0 && !d[v]){
                d[v]=d[u]+1;
                q.push(v);
            }
        }
    }
    return d[t]!=0;
}
int dfs(int s,int a){
    if(s==t || a==0) return a;
    int flow=0,f;
    for(int i=head[s];i!=-1;i=e[i].next){
        int v=e[i].v;
        if(d[v]!=d[s]+1) continue ;
        f=dfs(v,min(a,e[i].c));
        if(f>0){
            e[i].c-=f;
            e[i^1].c+=f;
            flow+=f;
            a-=f;
            if(a==0) break;
        }
    }
    if(!flow) d[s]=-1;
    return flow;
}
int Dinic(){
    int flow=0;
    while(bfs())
        flow+=dfs(0,INF);
    return flow;
}
int check(int mid){
    memset(head,-1,sizeof(head));tot=0;
    for(int i=1;i<=cnt;i++) for(auto v:vec[i]) for(auto k:S[i]) adde(v,n+k,1);
    for(int i=1;i<=n;i++) adde(0,i,mid);
    for(int i=n+1;i<=n+n;i++) adde(i,t,mid);
    int max_flow=Dinic();
    if(max_flow==mid*n) return 1;
    return 0;
}
int main(){
    scanf("%d",&T);
    while(T--){
        memset(g,0,sizeof(g));memset(map,0,sizeof(map));
        scanf("%d%d%d",&n,&m,&F);
        for(int i=0;i<=t;i++) f[i]=i,g[i].clear(),vec[i].clear(),S[i].clear();
        for(int i=1,u,v;i<=m;i++){
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
        }
        for(int i=1,x,y;i<=F;i++){
            scanf("%d%d",&x,&y);
            int fx=find(x),fy=find(y);
            if(fx!=fy) f[fx]=fy;
        }
        cnt=0 ;
        for(int i=1;i<=n;i++){
            int fx=find(i);
            if(fx==i){
                vec[++cnt].push_back(i);
                map[fx]=cnt;
            }
        }
        for(int i=1;i<=n;i++)
            if(f[i]!=i) vec[map[f[i]]].push_back(i);
        for(int i=1;i<=cnt;i++){
            for(auto v:vec[i]){
                for(auto tmp:g[v]) S[i].insert(tmp);
            }
        }
        int l=0,r=n+1;
        while(l<r){
            int mid=(l+r)>>1;
            if(check(mid)) l=mid+1;
            else r=mid;
        }
        printf("%d\n",l-1);
    }
    return 0;
}
View Code

 

之后再说说二分图匹配。

其实二分图匹配就是我们之前分析的第一种思想,每次就不断地拆边就行了...

下面是用floyd传递闭包的二分图匹配:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 305;
int check[N],match[N];
int n,m,f,T;
int g[N][N];
int dfs(int s){
    for(int i=n+1;i<=2*n;i++){
        if(!g[s][i] || check[i]) continue ;
        check[i]=1;
        if(match[i]==-1 || dfs(match[i])){
            match[i]=s;
            return 1;
        }
    }
    return 0;
}
int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d%d%d",&n,&m,&f);
        memset(g,0,sizeof(g));
        for(int i=1;i<=m;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            g[u][v+n]=1;
        }
        for(int i=1;i<=f;i++){
            int u,v;
            scanf("%d%d",&u,&v);
            g[u][v]=g[v][u]=1;
        }
        for(int k=1;k<=n;k++)
            for(int i=1;i<=n;i++)
                for(int j=1;j<=2*n;j++)
                    g[i][j]=(g[i][k]&&g[k][j])||g[i][j];
        int ans = 0,flag=0;
        while(1){
            flag = 0;
            memset(match,-1,sizeof(match));
            for(int i=1;i<=n;i++){
                memset(check,0,sizeof(check));
                if(!dfs(i)){
                    flag=1;
                    break ;
                }
            }
            if(flag) break;
            for(int i=n+1;i<=2*n;i++)
                g[match[i]][i]=0;
            ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

posted @ 2018-12-15 20:55  heyuhhh  阅读(175)  评论(0编辑  收藏  举报