hdu 2255 二分图—最优匹配

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

/*

最优匹配:使二分图的边权和最大的完备匹配。(如果二分图的两个点集不相等可以通过加 ’点‘ 和 ’权值为0的边‘ 实现转化)

相关概念
可行顶标:若L(x)是顶点x对应的顶标值。可行顶标对于图中的每条边(x,y)都有L(x)+L(y)>=w(x,y)
相等子图:只包含L(x)+L(y)=w(x,y)的边的子图

算法流程

设顶点Xi的顶标为a[i],顶点Yi的顶标为b[i]

ⅰ.初始时,a[i]为与Xi相关联的边的最大权值,b[j]=0,保证a[i]+b[j]>=w(i,j)成立

ⅱ.当相等子图中不包含完备匹配时,就适当修改顶标以扩大相等子图,直到找到完备匹配为止

修改顶标的方法:当从 Xi 开始寻找交错路失败后,得到一棵交错树,对交错树中 X 顶点的顶标减少 d 值, Y 顶点的顶标增加 d 值。
对于图中所有的边 (i,j) 有:

i和j都不在交错树中,边(i,j)仍然不属于相等子图  
i和j都在交错树中,边(i,j)仍然属于相等子图
i不在交错树中,j在交错树中,a[i]+b[j]扩大,边(i,j)不属于相等子图
i在交错树,j不在交错树中,边(i,j)有可能加入到相等子图中

为了使a[i]+b[j]>=w(i,j)始终成立,且至少有一条边加入到相等子图中,d=min{a[i]+b[j]-w(i,j)},其中 i 在交错树中, j 不在交错树中。

由于在算法过程一直保持顶标的可行性,所以任意一个匹配的权值和肯定小于等于所有结点的顶标之和,又因为在扩大相等子图过程中优先加入了权值大大边,所以在相等子图中找到的第一个完备匹配就是最优匹配。
*/

题目大意:有n个人和n件物品,w[i][j]表示第 i 个人对第 j 件物品的出价,求这些物品能得到的最大价值;

#include<stdio.h>
#include<string.h>
#define N 305
#define Max 1000000
int w[N][N],linky[N];  //数组 w 记录边权,数组 linky 记录顶点 y 的匹配;
int lx[N],ly[N];       //数组 lx 和 ly 分别记录顶点 x 和 y 的顶标值;
int visx[N],visy[N];   //数组 visx 和 visy 分别记录顶点 x 和 y 是否被访问;
int n,low;             //low 记录每次顶标需要变化的值;
int Find(int x)
{
    int y,t;
    visx[x]=1;
    for(y=0;y<n;y++){
        if(visy[y])continue;
        t=lx[x]+ly[y]-w[x][y];
        if(t==0){
            visy[y]=1;
            if(linky[y]==-1||Find(linky[y])){
                linky[y]=x;
                return 1;
            }
        }
        else{
            if(low>t)low=t;
        }
    }
    return 0;
}
void KM()
{
    int i,x;
    for(x=0;x<n;x++){
        while(1){
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            low=Max;
            if(Find(x))break;
            for(i=0;i<n;i++){
                if(visx[i])
                    lx[i]-=low;
                if(visy[i])
                    ly[i]+=low;
            }
        }
    }
}
int main()
{
    int i,j,sum;
    while(scanf("%d",&n)!=EOF){
        memset(lx,0,sizeof(lx));
        memset(ly,0,sizeof(ly));
        memset(linky,-1,sizeof(linky));
		for(i=0;i<n;i++){
			for(j=0;j<n;j++){
				scanf("%d",&w[i][j]);
				if(lx[i]<w[i][j])
					lx[i]=w[i][j];
			}
		}
        KM();    
        for(i=0,sum=0;i<n;i++){
            sum+=w[linky[i]][i];
        }
        printf("%d\n",sum);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值