KM算法

初始化可行顶标的值
用匈牙利算法寻找相等子图的完备匹配
若未找到增广路则修改可行顶标的值
重复上述过程直到找到相等子图的完备匹配为止

/*
km算法
二分图最大权匹配
复杂度:O(V*E)
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3fffffff

int g[500][500];   //给定的二分图 
int ex1[500];      //左边的期望 
int ex2[500];      //右边的期望 
bool vis1[500];    //左边是否被访问过
bool vis2[500];    //右边是否被访问过 
int match[500];    //记录右边匹配到的编号
int slack[500];    //在每一轮匹配中记录右边能被左边匹配到最少还差多少期望

int n,m;   //n为二分图左边的数量,m为右边的数量 

bool dfs(int x)   //为编号为x的尝试匹配 
{
	vis1[x] = true;
	for (int i = 1; i <= m; i++)
	{
		if( g[x][i] == 0 ) continue;   //如果x与i没有边 
		if( vis2[i] ) continue;    //每一轮匹配右边只能被尝试一次
		int gap = ex1[x] + ex2[i] - g[x][i];
		if( gap == 0 )    //期望相加等于权值,i点可以尝试进行匹配 
		{
			vis2[i] = true;
			if( match[i] == -1 || dfs(match[i]) )   
			//如果该点没有匹配或该点匹配的点可以匹配新的点 
			{
				match[i] = x;
				return true;
			} 
		}else slack[i] = min(slack[i],gap);   
		//i点尝试匹配的资格都没有,更新距离成功被尝试匹配的期望差 
	 
	} 
	return false;
} 

int km()     //求出二分图的最大权匹配
{
	memset(match,-1,sizeof(match));   //初始match为-1 
	memset(ex2,0,sizeof(ex2));        //右边的期望为0
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			ex1[i] = max(ex1[i],g[i][j]);   //左边的期望是与其相连的最大权值 
		}
	} 
	for (int i = 1; i <= n; i++)    //为左边的每个点进行分配 
	{
		for (int j = 1; j <= m; j++)
		{
			slack[j] = INF;
		} 
		while(1)     //一轮匹配可能无法匹配到,所以是while 
		{
			memset(vis1,0,sizeof(vis1));
			memset(vis2,0,sizeof(vis2));
			if( dfs(i) ) break;     //找到就退出,找不到就要降低期望值 
			int d = INF;            //最少可降低的期望值 
			for (int j = 1; j <= m; j++)
			{
				if( !vis2[j] ) d = min(d,slack[j]);   
				/*取这轮匹配中的没被尝试匹配过的slack最小值
				  左边期望减少这些,就可以保证可以尝试匹配新的点 
				*/
			}
			for (int j = 1; j <= n; j++)
			{
				if( vis1[j] ) ex1[j] -= d;    //尝试匹配过的左边期望降低 
			}
			for (int j = 1; j <= n; j++)
			{
				if( vis2[j] ) ex2[j] += d;    //尝试匹配过的右边期望升高 
				else if( g[i][j] ) slack[j] -= d;           
				/*没有成功被尝试匹配的,并且与i有边相连 
				  由于尝试匹配的左边的点期望减少,所以j最少需要的期望减少
				*/ 
			} 
		} 
	}
	int ans = 0;
	for (int i = 1; i <= m; i++)
	{
		ans += g[match[i]][i];
	}
	return ans;
} 

int main()
{
	while( scanf("%d",&n) != EOF )
	{
		m = n;
		for (int i = 1; i <= n; i++)
		{
			for (int j = 1; j <= m; j++)
			{
				scanf("%d",&g[i][j]);
			}
		}
		printf("%d\n",km());
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值