匈牙利算(二分匹配法)入门

本文探讨了算法匹配问题,如过山车配对问题,使用匈牙利算法解决最大匹配数;并介绍了AirRaid问题,如何在有向无环图中寻找最少的不想交的简单路径覆盖所有顶点,涉及最小路径覆盖和二分图最大匹配的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法理解参考https://blog.youkuaiyun.com/sunny_hun/article/details/80627351

1.过山车

http://acm.hdu.edu.cn/showproblem.php?pid=2063

RPG girls今天和大家一起去游乐场玩,终于可以坐上梦寐以求的过山车了。可是,过山车的每一排只有两个座位,而且还有条不成文的规矩,就是每个女生必须找个个男生做partner和她同坐。但是,每个女孩都有各自的想法,举个例子把,Rabbit只愿意和XHD或PQK做partner,Grass只愿意和linle或LL做partner,PrincessSnow愿意和水域浪子或伪酷儿做partner。考虑到经费问题,boss刘决定只让找到partner的人去坐过山车,其他的人,嘿嘿,就站在下面看着吧。聪明的Acmer,你可以帮忙算算最多有多少对组合可以坐上过山车吗?

Input

输入数据的第一行是三个整数K , M , N,分别表示可能的组合数目,女生的人数,男生的人数。0<K<=1000
1<=N 和M<=500.接下来的K行,每行有两个数,分别表示女生Ai愿意和男生Bj做partner。最后一个0结束输入。

Output

对于每组数据,输出一个整数,表示可以坐上过山车的最多组合数。

Sample Input

6 3 3
1 1
1 2
1 3
2 1
2 3
3 1
0

Sample Output

3

 


//此题是求最大匹配数,用匈牙利算法

#include<stdio.h>

#include<string.h>



int line[505][505],boy[505],flag[505];     //line表示两人是否有关系,boy表示该男生的女伴,flag表示该男生是否被询问过

int n,m;         //m为女生数量,n为男生数量



int find(int x)    //为任意一个女生寻找最佳伴侣,找到则返回1

{

	for(int k=1;k<=n;k++)  //逐个扫描每个男生

		if(line[x][k]==1&&flag[k]==0)      //若两人之间有关系且在这次查找中没有被询问

		{

			flag[k]=1;                      //标志该男生在此次查找中已被询问

			if(boy[k]==0||find(boy[k]))  //如果该男生没有女伴或者该男生的女伴可以找到其他男生,则匹配成功,这是一个递归过程

			{

				boy[k]=x;                //符合条件则将该女生和该男生匹配上,并标记

				return 1;

			}

		 }

	return 0;

}



int main()

{

	int k;

	int  i,a,b;

	while(scanf("%d",&k)!=EOF&&k)

	{

		scanf("%d%d",&m,&n);

		int count=0;

		memset(line,0,sizeof(line));

		memset(boy,0,sizeof(boy));

		for(i=1;i<=k;i++)

		{

			scanf("%d%d",&a,&b);

			line[a][b]=1;	      //表示女生a和男生b有关系

		}

		for(i=1;i<=m;i++)         //逐个扫描每个女生,为她们寻找最佳伴侣

		{

			memset(flag,0,sizeof(flag));    //每次循环将标志都清零

			if(find(i))           //如果找到,则最大匹配数加1

				count++;

		}

		printf("%d\n",count);

	}

	return 0;

 }


2.Air Raid

http://acm.hdu.edu.cn/showproblem.php?pid=1151

以一个城镇为例, 那里所有的街道都是单向的, 每条街道都从一个十字路口通往另一个十字路口。人们还知道, 从一个十字路口开始, 穿过城镇的街道, 你永远无法到达同一个十字路口, 即城镇的街道没有周期。
有了这些假设, 你的任务是写一个程序, 找到可以下到镇上的伞兵的最小数量, 并访问这个小镇的所有路口, 这样一个以上的伞兵访问没有交叉
口。每个伞兵都落在一个十字路口, 可以参观城镇街道之后的其他路口。对于每个伞兵的起始交叉口没有任何限制。

输入

您的程序应读取数据集。输入文件的第一行包含数据集的数量。每个数据集指定一个城镇的结构, 并具有格式:


没有 _ 交叉口没有 _ 街道
s1
e1 s2 e2
..。
sno _ of 街道 eno _ of

街道每个数据集的第一行包含 _ 交叉口的正整数 no _ (大于0和小于或等于 120), 即城镇中的交叉点的数量。第二行包含一个正整数 no _ of 街道, 即镇中的街道数量。下一个没有街道的线路, 镇上的每条街道都有一条, 是随机订购的, 代表着镇上的街道。与街道 k (k < = no _ 街道) 相对应的行由两个正整数组成, 由一个空白分隔: sk (1 < = sk < = _ 交叉口的十字路口的编号)--街道起点的交叉口的编号, 以及 ek (1 < = ek < = no _交叉点)-是街道尽头的交叉点的编号。交集由 _ 交叉点的1到 no _ 的整数表示。
连续的数据集之间没有空
行。输入数据是正确的。

输出

该程序的结果是在标准输出上。对于每个输入数据集, 程序打印在一行上, 从行的开头开始, 一个整数: 访问镇上所有路口所需的最小伞兵数。

样品输入

2
4
3
3 4
1 3
2 3
3
3
1 3
1 2
2 3

样品输出

2
1

题意:

城镇里的街道从一个交叉口连接到另一个交叉口,街道都是单向的,并且从一个交叉口沿着街道出发不会回到相同的交叉口。
伞兵降临在城镇的一个交叉口并可以沿着街道走向另一个没有被其他伞兵走过的交叉口
问城镇中的所有交叉口都被伞兵走过的情况下至少需要多少名伞兵。

要求:输入一个数字T,表示T组数据,然后每组数据前两行分别为一个数字n,m,n代表n个顶点,m表示m条边,求出在连通情况下的最少路径覆盖。

 

扩展题型

在一个有向无环图中,从一些顶点出发,能遍历到图上所有点,要求初始选择的顶点数最少且顶点不重复遍历。

思路:
如果从某个顶点开始遍历的过程看成是路径的选择

----------------------------------问题转化为-------------------------

在有向无环图中找最少的不想交的简单路径,这些路径覆盖图中的所有顶点。可见是关于最小路径覆盖的问题。

在有向无环图中,最小路径覆盖数  = 节点数 — 其对应二分图的最大匹配数。

最小路径覆盖它要求原图必须是有向无环图。

 

根据原图构造二分图,方法为:将原图中的每个顶点vi一分为二,相当于复制一个有向图,通过两个图的顶点根据数据关系进行匹配(把能匹配点的店匹配可构成便一条路),两个这就构造出一个路径覆盖问题所对应的二分图,在该二分图的基础上求其最大匹配数。
 


#include<bits/stdc++.h>
using namespace std;
int ship[500][500];
int vis[500];
int s[500];
int a,b;
int find(int x)
{
    for(int i=1;i<=a;i++)
    {
        if(!vis[i]&&ship[i][x])
        {
            vis[i]=1;
            if(!s[i]||find(s[i]))
            {
                s[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    int n;
    cin>>n;
    while(n--)
    {

        cin>>a>>b;
        memset(ship,0,sizeof(ship));
        memset(s,0,sizeof(s));
        int y,x;
        for(int i=1;i<=b;i++)
        {

            cin>>x>>y;
            ship[x][y]=1;
        }
        int count=0;
        for(int i=1;i<=a;i++)
        {
            memset(vis,0,sizeof(vis));
            if(find(i))
                count++;

        }
        int ans=a-count;
        cout<<ans<<endl;
    }
     return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值