二分图模板总结 nyoj 239 月老的难题 && poj 1468 COURSES

来源:互联网 发布:小学教师网络研修计划 编辑:程序博客网 时间:2024/06/05 20:20

 二分图最大匹配 匈牙利算法 模板题

月老的难题

时间限制:1000 ms  |  内存限制:65535 KB
难度:4
描述

月老准备给n个女孩与n个男孩牵红线,成就一对对美好的姻缘。

现在,由于一些原因,部分男孩与女孩可能结成幸福的一家,部分可能不会结成幸福的家庭。

现在已知哪些男孩与哪些女孩如果结婚的话,可以结成幸福的家庭,月老准备促成尽可能多的幸福家庭,请你帮他找出最多可能促成的幸福家庭数量吧。

假设男孩们分别编号为1~n,女孩们也分别编号为1~n。

输入
第一行是一个整数T,表示测试数据的组数(1<=T<=400)
每组测试数据的第一行有两个整数n,K,其中男孩的人数与女孩的人数都是n。(n<=500,K<=10 000)
随后的K行,每行有两个整数i,j表示第i个男孩与第j个女孩有可能结成幸福的家庭。(1<=i,j<=n)
输出
对每组测试数据,输出最多可能促成的幸福家庭数量
样例输入
13 41 11 32 23 2
样例输出
2

这里用邻接表存储,还可以用邻接矩阵,hash等

这几位大神写的更详细些,可以再参考参考:二分图的最大匹配  

                                                                               匈牙利算法求二分图的最大匹配

                                                                                匈牙利算法模板

模板中核心部分都一样,我用的int 型map和vis数组(vector中用link)进行标记(0或1),其实也可以用bool 型,这样更能节省空间

#include<algorithm>#include<cstring>#include<stdio.h>#include<vector>using namespace std;#define MAXN 550vector<int> v[MAXN];/*用STL中的vector建立邻接表实现匈牙利算法效率比较高*/int vis[MAXN];    //记录v[x]中结点是否访问过int link[MAXN];int n,m;bool find(int x){    for(int j= 0 ; j < v[x].size(); j++)    {        if(vis[v[x][j]]==0)//如果节点i与j相连并且未被查找过        {            vis[v[x][j]]=1; //标记i为已查找过            if(link[v[x][j]]==0||find(link[v[x][j]]))            {                //如果j是起始节点或者与j相连的结点出发有增广路径                link[v[x][j]]=x;                return true;            }        }    }    return false;}int MaxMatch(){    memset(link,0,sizeof(link));    int count = 0 ;    for(int i = 1; i <= n ; i++)    {        memset(vis,0,sizeof(vis));        if(find(i))            count ++;    }    return count;}int main(){    int N,star,to;    scanf("%d",&N);    while(N--)    {        memset(v,0,sizeof(v));        scanf("%d%d",&n,&m);        for(int i = 0 ; i < m ; i++)        {            scanf("%d%d",&star,&to);            v[star].push_back(to);        }        printf("%d\n",MaxMatch());    }    return 0;}


 

若用邻接矩阵存,超时,不过这种方法可以做 poj1469 COURSES 这题

#include<algorithm>#include<cstring>#include<stdio.h>#include<vector>using namespace std;#define MAXN 550int map[MAXN][MAXN];int vis[MAXN];int link[MAXN];int n,m;bool find(int x){    for(int i = 1; i <= m;i++)    {        if(vis[i]==0&&map[x][i])        {            vis[i]=1;            if(link[i]==0 || find(link[i]))            {                link[i]=x;                return true;            }

        }    }    return false ;}int MaxMatch(){    memset(link,0,sizeof(link));    int count = 0 ;    for(int i = 1; i <= n ; i++)    {        memset(vis,0,sizeof(vis));        if(find(i))        count ++;    }    return count ;}int main(){    int T;    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&m);        memset(map,0,sizeof(map));        for(int i = 1 ; i <= n ; i++)        {            int t;            scanf("%d",&t);            for(int j = 1 ; j <= t ; j++){                int k ;                scanf("%d",&k);                map[i][k]=1;            }        }        if(MaxMatch()== n)            printf("YES\n");        else            printf("NO\n");    }    return 0;}

 

变1 :
在二分图中求最少的点,让每条边都至少和其中的一个点关联,这就是“二分图的最小顶点覆盖”:

二分图的最小顶点覆盖数 = 二分图的最大匹配数

例题:hdu 1150 Machine Schedule

#include<algorithm>#include<cstring>#include<stdio.h>#include<vector>using namespace std;#define MAXN 550int map[MAXN][MAXN];int vis[MAXN];int link[MAXN];int n,m;bool find(int x){    for(int i = 1; i <= n; i++)    {        if(vis[i]==0&&map[x][i]==1 )        {            vis[i]=1;            if(link[i]==0 || find(link[i]))            {                link[i]=x;                return true;            }        }    }    return false ;}int MaxMatch(){    memset(link,0,sizeof(link));    int count = 0 ;    for(int i = 1; i <= m ; i++)//这里的m和find函数里的n可以互换    {//原因是都是在二分图的一个集合中遍历另一个集合的最优匹配        memset(vis,0,sizeof(vis));//所以两个集合谁遍历谁都一样        if(find(i))            count ++;    }    return count ;}int main(){    while(scanf("%d",&n),n)    {        int k;        scanf("%d%d",&m,&k);        memset(map,0,sizeof(map));        for(int i = 1 ; i <= k ; i++)        {            int a,b,c;            scanf("%d%d%d",&c,&a,&b);            map[a][b]=1;        }        printf("%d\n",MaxMatch());    }    return 0;}



 

 

变2:DAG图(无回路有向图)的最小路径覆盖
用尽量少的不相交简单路径覆盖有向无环图(DAG)的所有顶点,这就是DAG图的最小路径覆盖问题。
DAG图的最小路径覆盖数 = 节点数(n)- 最大匹配数(m)

例题:hdu 1151 Air Raid

这题直接套前面的模板就行,把输入格式改改,最后用n - MaxMatch()得到答案

 

变3: 二分图的最大独立集
二分图的最大独立集数 = 节点数(n)- 最大匹配数(m)
关键:求二分图的最大匹配数

例题:hdu 1068 Girls and Boys

      

此题相当于有向图,最大匹配/2                    

#include<algorithm>#include<cstring>#include<stdio.h>#include<vector>using namespace std;#define MAXN 550int map[MAXN][MAXN];int vis[MAXN];int link[MAXN];int n,m;bool find(int x){    for(int i = 0; i < n; i++)    {        if(vis[i]==0&&map[x][i]==1 )        {            vis[i]=1;            if(link[i]==0 || find(link[i]))            {                link[i]=x;                return true;            }        }    }    return false ;}int MaxMatch(){    memset(link,0,sizeof(link));    int count = 0 ;    for(int i = 0; i < n ; i++)    {        memset(vis,0,sizeof(vis));        if(find(i))            count ++;    }    return count ;}int main(){//注意每一题的取值范围,此题为0--n-1    while(scanf("%d",&n)!=EOF)    {        memset(map,0,sizeof(map));        for(int i = 0 ; i < n ; i++)        {            int a,b,c;            scanf("%d: (%d)",&a,&b);            for(int j = 0 ; j < b ; j++)            {                scanf("%d",&c);                map[a][c]=1;            }        }        printf("%d\n",(n-MaxMatch()/2));//这题相当于有向图,循环两遍因此除以2    }    return 0;}

  

0 0