图论-最大二分匹配-匈牙利算法&最大流

先丢例题(又是奶牛题)

The Perfect Stall

Time limit per test: 1.0 seconds

Memory limit: 256 megabytes

Farmer John completed his new barn just last week, complete with all the latest milking technology. Unfortunately, due to engineering problems, all the stalls in the new barn are different. For the first week, Farmer John randomly assigned cows to stalls, but it quickly became clear that any given cow was only willing to produce milk in certain stalls. For the last week, Farmer John has been collecting data on which cows are willing to produce milk in which stalls. A stall may be only assigned to one cow, and, of course, a cow may be only assigned to one stall.

Given the preferences of the cows, compute the maximum number of milk-producing assignments of cows to stalls that is possible.

Input

The input includes several cases. For each case, the first line contains two integers, N (0N200) and M (0M200). N is the number of cows that Farmer John has and M is the number of stalls in the new barn. Each of the following N lines corresponds to a single cow. The first integer (Si) on the line is the number of stalls that the cow is willing to produce milk in (0SiM). The subsequent Si integers on that line are the stalls in which that cow is willing to produce milk. The stall numbers will be integers in the range (1M), and no stall will be listed twice for a given cow.

Output

For each case, output a single line with a single integer, the maximum number of milk-producing stall assignments that can be made.

 

看起来有点复杂,实际上是在求一个最大二分匹配。那什么是最大二分匹配?这是一个图论的概念,首先要弄清楚下面的几个概念:

二分图:对于一个无向图G(V,E),如果能分成两个互不相交的子集,且所有的边都跨过两个子集,则称G为一个二分图;

匹配:图中一个边的集合,且这个集合中的边没有公共顶点;

交替路:从一个未匹配点出发,依次经过非匹配边、匹配边、非匹配边…形成的路径叫交替路;

增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path);

搞清楚了这四个概念,那么最大二分匹配其实就是求一个二分图中最大的匹配。

如果还看不懂,也可以认为就是让两个集合合法的两两配对的对数最多。

 

解这个问题有两种解法:最大流和匈牙利算法,这里介绍匈牙利算法(最大流见下)

 

匈牙利算法的原理简单来说,就是依次对上述的一个集合的所有点求增广路。具体流程如下:

1.选择一个还没有匹配过的点A,清空搜索标记;

2.寻找一个未被标记过的点,记录匹配,打上搜索标记;

3.如果找到的点被标记过,对这个点的匹配点进行2.;

也就是说,我们希望占住合法匹配点的前面的点们能够把点让出来,这样现在的点就有得匹配了。

 

顺便说一下,匈牙利算法的复杂度为O(VE);

 

下面放代码:

 

struct cow1{
 int stall[205];
}cow[205];//每头cow的第i个stall能不能挤奶
int state[201],result[201];//stata:搜索标记;result:某牛栏对应的牛,也就是匹配结果
int n,m;

关键递归部分

int find(int x){
    for(int i=1;i<=m;i++){
        if(cow[x].stall[i]==1 && !state[i]){
           state[i]=1;//标记为搜索过
           if(result[i]==0 || find(result[i])){//未被匹配过||递归试试这个点能不能被让出来
              result[i]=x;//匹配i,x
              return 1;//能找到新的匹配
           }
        }
    }
    return 0;
}

 主程序

int main(){
    int n1,t;
    while(cin>>n>>m){
    ans=0;
    memset(cow,0,sizeof(cow));
    memset(result,0,sizeof(result));
   
    for(int i=1;i<=n;i++){
        cin>>n1;
        for(int j=1;j<=n1;j++){
            cin>>t;
            cow[i].stall[t]=1;
        }
    }
    for(int i=1;i<=n;i++){
        memset(state,0,sizeof(state));//清空搜索标记
        ans+=find(i);//找到新的匹配
    }
    cout<<ans<<endl;
}
    return 0;
}

 最近学了点网络流,尝试用最大流的方法解决这个问题

如果转化为网络流问题,其实就是在流量均为1的图中构造超级源点和超级汇点,然后求一个最大流

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int MAX;
int mapping[1005][1005];
int pre[1005],m;
bool vis[1005]; 
int bfs(int s,int t){
    memset(pre,0,sizeof(pre));
    memset(vis,0,sizeof(vis));
    int n,i,j;
    
    vis[s]=1;
    int mi=MAX*2,cur;
    queue<int>que;
    que.push(s);
    while(!que.empty()){
        cur=que.front();
        que.pop();
        if(cur==t) break;
        for(i=0;i<=t;i++){
            if(!vis[i]&&(mapping[cur][i]>0)){
                que.push(i);
                mi=min(mi,mapping[cur][i]);
                pre[i]=cur;
                vis[i]=1;
            }
        }
        
    }
    while(!que.empty()) que.pop();
    if(!pre[t]) return 0;
    return mi;
}
void update(int t,int newf){
    while(t){
        mapping[pre[t]][t]-=newf;
        mapping[t][pre[t]]+=newf;
        t=pre[t];
    }
}
int maxF(int s,int t){
    int maxf(0),newf(t);
    do{
        newf=bfs(s,t);
        update(t,newf);
        maxf+=newf;
    }while(newf);
    
    return maxf;
}
int main(){
    
    int n,i,j,k;
    while(cin>>n>>m){
        MAX=n+m+1;
        memset(mapping,0,sizeof(mapping));
        for(i=1;i<=n;i++){
        scanf("%d",&j);
        mapping[0][i]=1;
        while(j--){
            scanf("%d",&k);
            mapping[i][k+n]=1;
            mapping[k+n][MAX]=1;
        }
    }
    
    cout<<maxF(0,MAX)<<endl;
    }
    
    
}

 

资料来源:

http://www.cnblogs.com/zjutlitao/p/3528203.html

https://blog.csdn.net/sunny_hun/article/details/80627351

https://www.cnblogs.com/wangjunyan/p/5563154.html

posted @ 2018-10-29 21:17  wengsy150943  阅读(445)  评论(0)    收藏  举报