HDU 2853 Assignment(KM匹配)

题目链接:  http://acm.hdu.edu.cn/showproblem.php?pid=2853

 

因为我们要变动最小,所以对在原计划中的边要有一些特殊照顾,使得最优匹配时,尽量优先使用原计划的边,这样变化才能是最小的且不会影响原匹配。

根据这个思想,我们可以把每条边的权值扩大k倍,k要大于n。然后对原计划的边都+1。精华全在这里。我们来详细说明一下。

全部边都扩大了k倍,而且k比n大,这样,我们求出的最优匹配就是k倍的最大权值,只要除以k就可以得到最大权值。实现原计划的边加1,这样,在每次选择边时,这些变就 有了优势,就会优先选择这些边。假如原计划的h条边被选入了最优匹配中,这样,最优权值就是k倍的最大权值+k(原计划的每条边都+1)。但是k大于n的用意何在呢?我们发现假如原计划的边全部在匹配中,只会增加n,又n<k,所以除以k后不会影响最优匹配的最大权值之和,然后我们对k取余,就正好得到加入的原计划的边的个数。这时,我们只需要用总点数-加入的原计划的点数,就可以求得最小变动数了。

思路果然巧妙,既然自己想不出来,就见一个记住一个吧。积少成多。

 

代码:

#include<stdio.h>
#include<string.h>
#define MAX 55

int map[MAX][MAX],match[MAX],m,n;
int sx[MAX],sy[MAX],lx[MAX],ly[MAX];

int Match(int u)
{
   int j;
   sx[u]=1;
   for(j=1;j<=m;j++)
       if(!sy[j] && map[u][j]==lx[u]+ly[j])
       {
           sy[j]=1;
           if(!match[j] || Match(match[j]))
           {
               match[j]=u;
               return 1;
           }
       }
       return 0;
}

void KM_Match()
{
   int i,j,k,min;
   for(i=1;i<=n;i++)
   {
       lx[i]=map[i][1];
       for(j=2;j<=m;j++)
           lx[i]=lx[i]>map[i][j]?lx[i]:map[i][j];
   }
   memset(ly,0,sizeof(ly));
   memset(match,0,sizeof(match));
   for(i=1;i<=n;i++)
   {
       while(1)
       {
           memset(sx,0,sizeof(sx));
           memset(sy,0,sizeof(sy));
           if(Match(i))break;
           min=0xfffffff;
           for(j=1;j<=n;j++)
           {
               if(sx[j])
               {
                   for(k=1;k<=m;k++)
                       if(!sy[k])
                           min=min>(lx[j]+ly[k]-map[j][k])?(lx[j]+ly[k]-map[j][k]):min;
               }
           }
           for(j=1;j<=n;j++)if(sx[j])lx[j]-=min;
           for(j=1;j<=m;j++)if(sy[j])ly[j]+=min;
       }
   }
}


int main()
{
   int i,j,sum,res;
   while(scanf("%d%d",&n,&m)!=EOF)
   {
       res=0;
       for(i=1;i<=n;i++)
           for(j=1;j<=m;j++)
           {
               scanf("%d",&map[i][j]);
               map[i][j]*=100;
           }
       for(i=1;i<=n;i++)
       {
           scanf("%d",&match[i]);
           map[i][match[i]]++;
           res+=map[i][match[i]];
       }
       KM_Match();
       sum=0;
       for(i=1;i<=m;i++)
           if(match[i])sum+=map[match[i]][i];
       printf("%d %d\n",res%100-sum%100,sum/100-res/100);
   }
   return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值