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

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

月老的难题

时间限制: 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)
输出
对每组测试数据,输出最多可能促成的幸福家庭数量
样例输入
1
3 4
1 1
1 3
2 2
3 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 550
vector<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 550 int 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 550
int 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 550
int 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;
}

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值