KM

<span style="font-size:18px;">km算法,求完美匹配下的最大(小)权匹配。 在一个二分图内,左顶点为X,右顶点为Y,现对于每组左右连接Xi,Yj有权wij,求一种匹配使得所有wij的和最大。
 //O(n^3) hdu 2255
//求最大权匹配
//若求最小权匹配,可将权值取相反数,结果取相反数
/*
预处理lx[i]=max(w[i][j]);memset(ly,0,sizeof(ly));
所以对于任何边:lx[i]+l[y]>=w[i][j]
设:左边已经匹配好的点S标记为1,没匹配的点S'标记为0。右边已经匹配好的点T标记为1,没匹配的点T'标记为0。
大体思路:
①:枚举左边的每个点去匹配右边的点,如果有点满足lx[i]+ly[j]==w[i][j],就让这两个点暂时匹配
如果不满足(即无法在满足lx[i]+ly[j]==w[i][j]匹配到右边的点),则更新。

②更新:目的是让左边已经匹配好的点让出匹配,让新加的点与之(lx[i]+ly[j]==w[i][j])的点可能出现匹配,假设i'点与j'点已经匹配好了,i'点要让出来,它找右边没有被匹配的点(j'')匹配,同时要保证权值最大。由于lx[i']+ly[j'']>=w[i'][j'']
要是等式取等,那么左边肯定要减去一个值a。即要使lx[i']-a+ly[j'']==w[i'][j'']。当a取最小的一个值时,新加的匹配边得到的权值就最大,可以想象成左边的i为了匹配到点,牺牲了a,即最大全减小了a。
a=lx[i']+ly[j'']-w[i'][j'']。a值可以在之前匹配的时候处理出来(减少时间复杂度)。

③由于是假设的i'点要让出来,但实际是不知道是在S集合中的那个i'点让出来,所以S集合中的i'点lx[i']-=a;
为了保证S集合中的点和T集合中的点匹配,ly[j']+=a;(j’属于T集合),则lx[i']-a+ly[j']+1==w[i'][j']同样满足匹配条件。
这样i'就和j''匹配了,而新加进来的i可能会找到lx[i]+ly[j]==w[i][j]的点与之匹配。若还是找不到,那自然就和j''匹配了。
这样就加入了左边一个点和右边一个点,并且保证了匹配的权值最大。

④更新完后S.T集合是清零了的,然后再从新加入的i点在匹配并标记好S、T集合
*/
#include <iostream>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <string.h>
#include <map>
#include <set>
using namespace std;
#define MAXN 505
#define INF 1<<30
int w[MAXN][MAXN],n,m;//w[i][j],i-j的权
int lx[MAXN],ly[MAXN];
int r[MAXN],slack[MAXN];
//r[j]记录哪个点连j点达最优、slack[j]=min(lx[i]+ly[j]-w[i][j]);减少复杂度
bool s[MAXN],t[MAXN];//s标记左边的点,t标记右边的点
bool match(int i)
{
    s[i]=true;
    for(int j=1;j<=m;j++)
    {
        int temp=lx[i]+ly[j]-w[i][j];
        if(temp==0&&!t[j])
        {
            t[j]=true;
            if(!r[j]||match(r[j]))
            {
                r[j]=i;
                return true;
            }
        }
      if(!t[j])
        slack[j]=min(slack[j],temp);
    }
    return false;
}
void update()
{
    int i,j;
    int a=INF;
  /*  for(i=1;i<=n;i++)
    {
        if(s[i])
        for(j=1;j<=m;j++)
        {
            if(!t[j])
            {
              a=min(a,lx[i]+ly[j]-w[i][j]);
            }
        }
    }*/ //未用slack[]优化 多一层循环;
    for(j=1;j<=m;j++)
        if(!t[j])//找出右边未匹配的点的slack最小值
         a=min(a,slack[j]);
    for(i=1;i<=n;i++)
        if(s[i])lx[i]-=a;//左边匹配的点lx-=a;
    for(j=1;j<=m;j++)
       if(t[j])ly[j]+=a;//右边匹配的点ly+=a;
}
void km()
{
    int i,j;
    memset(lx,0,sizeof lx);
    memset(ly,0,sizeof ly);
    memset(r,0,sizeof r);
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
        {//初始化使左边为i点能获得的最大权值,右边ly[]为0
            lx[i]=max(lx[i],w[i][j]);
        }
    }
    for(i=1;i<=n;i++)
    {
        for(int y=1;y<=m;y++)
            slack[y]=INF;
        while(1)
        {
            memset(s,0,sizeof s);
            memset(t,0,sizeof t);
             if(match(i))break;
                else update();//未能找到匹配点就更新
        }
    }
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        m=n;//此题m==n,m大小因题而异
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&w[i][j]);
            }
        }
        km();
        int ans=0;
        for(int j=1;j<=m;j++)
        {
            if(r[j])
            {
                ans+=w[r[j]][j];
            }
        }
        printf("%d\n",ans);
    }
  return 0;
}
</span>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值