NKOJ-1520 完美的牛栏(二分图 匈牙利算法-理解)

P1520完美的牛栏
时间限制 : 10000 MS 空间限制 : 65536 KB
问题描述
农夫约翰上个星期刚刚建好了他的新牛棚,他使用了最新的挤奶技术。不幸的是,由于工程问题,每个牛栏都不一样。第一个星期,农夫约翰随便地让奶牛们进入牛栏,但是问题很快地显露出来:每头奶牛都只愿意在她们喜欢的那些牛栏中产奶。上个星期,农夫约翰刚刚收集到了奶牛们的爱好的信息(每头奶牛喜欢在哪些牛栏产奶)。一个牛栏只能容纳一头奶牛,当然,一头奶牛只能在一个牛栏中产奶。

给出奶牛们的爱好的信息,计算最大分配方案。

输入格式
第一行 两个整数,N (0 <= N <= 200) 和 M (0 <= M <= 200) 。N 是农夫约翰的奶牛数量,M 是新牛棚的牛栏数量。

第二行到第N+1行 一共 N 行,每行对应一只奶牛。第一个数字 (Si) 是这头奶牛愿意在其中产奶的牛栏的数目 (0 <= Si <= M) 。后面的 Si 个数表示这些牛栏的编号。牛栏的编号限定在区间 (1..M) 中,在同一行,一个牛栏不会被列出两次。

输出格式
只有一行。输出一个整数,表示最多能分配到的牛栏的数量.

样例输入
5 5
2 2 5
3 2 3 4
2 1 5
3 1 2 5
1 2

样例输出
4

来源 usaco 4.2.2 二分图匹配

先分析一下(由于是理解帖就正经一点)

首先 每只牛都只能独自待在一个牛栏里
其次 牛与牛之间的喜好可能一样,所以可能会发生冲突

所以如果我们将牛和栏看起点和终点的话
就有

多个起点
每个起点对应多个终点

且题目要求
每个起点都只能对应唯一一个终点
每个终点都只能对应唯一一个起点

再进行深入的分析

一条边代表的是一个起点对应一个终点
由于每个起点和终点对应的关系是唯一的
所以结果应该是求最多条没有公共端点的边

引入定义

上述例子当中,可以将所有的点分成两堆,一堆是牛,一堆是栏
假设我们将牛放在上面,把栏放在下面
那么 我们可以轻易地发现,上面的点互相是没有联系的,下面的点也是一样
所有的联系(边)都仅存在于上面的点和下面的点之间
这样的图我们称为二分图

而上述例子当中,题中所求的是 没有公共端点的边的条数
这样的边组成的一个集合称为一个匹配
而题目的结果 最多的没有公共端点的边的条数 称为这个二分图的最大匹配

例如

我们将1 3 5 7 9放在上面,
     2 4 6 8 10放在下面
那么构图
1-2 1-4 3-4
3-8 5-6 9-10
7-4 3-6 5-10
(自行画出)
其中
    集合{1-2,2-4,3-6,5-10}可以称为一个匹配,因为集合中所有的边都没有公共交点
    集合{1-2,3-8,5-6,7-4,9-10}可称为该图的最大匹配,因为集合中的边的条数已经是最大了(找不到一个更大的匹配)

匈牙利算法

如何求一个二分图的最大匹配呢?

举例

已知边1-3,与3相连的点还有5,与1相连的点还有7
即 图 1-3 1-7 3-5
那么{1-3}是该小图的一个匹配
那么我们将这个匹配中的边扩展为7-1-3-5
断开原来的匹配边得到
{1-7 3-5}的新的更大的匹配

其中
扩展得到的那条链称为增广路
我们通过扩展已知的最长匹配得到增广路,再通过删除已知匹配边的操作来得到新的更大的匹配

注意

增广路扩展的要求是,扩展的点在原匹配当中没有出现过

附上增广路代码

bool find(int s)
{
    for(int e=1;e<=n;e++)
    if(map[s][e]&&!went[e])
    {
        went[e]=1;
        if(!link[e]||find(link[e]))
        {
            link[e]=s;
            return 1;
        }
    }
}

解释

went为标记已经加入匹配的点数组

枚举终点e,如果存在边 s-e且e没有加入匹配,那么将e加入匹配
判断 如果e没有匹配的起点,那么将s设为其起点,那么就增加了新的匹配边s-e
    如果e已经有匹配的起点link[e],那么就尝试扩展link[e],若可以扩展,那么就将e扩展到s下
    具体如下:
        e-link[e]为已知匹配边
        现在有s-e,link[e]-sl
        我们调用find(link[e]),link[e]可以扩展得到新的匹配边link[e]-sl
        那么我们扩展得到s-e-link[e]-sl

这个函数理解到了那么匈牙利算法就已经理解到一半了
剩下的一半

for(int i=1;i<=n;i++)
    if(find[i])res++;

可以理解为我们枚举上面一层的点,进行扩展
如果扩展成功,那么匹配边的条数一定能够且仅能够增加一条
而如果失败,那么这个点就一定是无法得到扩展的,那么即使其它点扩展完之后这个点再扩展一次也没用(自行理解)
因此只需要遍历一次上面的点,进行扩展
即可得到最大匹配的边数

解题

这道题几乎就是这个代码的模板题
只需要将奶牛分成一层,栅栏分成一层即可构成二分图
牛奶之间不可共用栅栏即为边与边之间没有公共端点
题目即为求最大匹配数

附上代码

#include 
#include 
#include 
using namespace std;

int res=0,n,m,link[456],went[456];
int maps[456][456];

bool find(int s)
{
    for(int e=1;e<=m;e++)
        if(maps[s][e]&&!went[e])
        {
            went[e]=1;
            if(!link[e]||find(link[e]))
            {
                link[e]=s;
                return 1;
            }
        }
    return 0;
}

int main()
{
    int c,e;
    scanf("%d%d",&n,&m);
    for(int s=1;s<=n;s++)
    {
        scanf("%d",&c);
        for(int i=1;i<=c;i++)
        {
            scanf("%d",&e);
            maps[s][e]=1;
        }
    }
    for(int s=1;s<=n;s++)
    {
        memset(went,0,sizeof(went));
        if(find(s))res++;
    }
    printf("%d",res);
}

你可能感兴趣的:(NKOI,图论)