[HDU2853]Assignment(KM双元限制)

题目:

我是超链接

题意:

有n个人和m个任务,每一个人要分配到一个任务,每一个人分配到一个任务会有一个价值。
现已知道一个初始的分配任务的方案,要求对这种方案进行调整,使在价值最大的前提下,每个人的任务修改的个数最少。

题解:

这道题目有两个限制,我们可以考虑这种问题的一种常见解法

双元限制讲解:
一般来说,如果有两个变量v1和v2需要优化,
要求首先满足v1最大,在v1相同的情况下v2最小,
这个时候我们一般会把这两个变量合成一个量:

M*v1+v2

(其中M是一个很大的量)
当X=M*v1+v2取最大值的时候
v1=X/M
v2=X%M
准确的说,M是一个比“v2的最大理论值和v2的最小理论值之差”还要大的数
这样,只要两个解的v1不同,则不管v2差多少,都是v1起决定性作用
只有v1相同时,才取决于v2

那么对于这道题目来说,M取100(>50)就好了(因为最多有50条边被钦定),然后给每一个“钦定”的任务边权+1,这样可以保证即使跑出来的最大值一开始会一样,但这个+1就很有用啦
因为是人-任务一一对应,用KM求出X的值,那么总价值v1,修改的边n-v2

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 1e9
using namespace std;
int n,m,e[55][55],vis[200],belong[55],delta[55],lx[55],ly[55];
bool find(int i,int id)
{
    vis[i]=id;
    for (int j=1;j<=m;j++)
      if (vis[j+n]!=id)
      {
        if (lx[i]+ly[j]==e[i][j])
        {
            vis[j+n]=id;
            if (!belong[j] || find(belong[j],id))
            {
                belong[j]=i;
                return 1;
            }
        }else delta[j]=min(delta[j],lx[i]+ly[j]-e[i][j]);
      }
    return 0;
}
int KM()
{
    int i,j,ans=0,id=0,ii;
    memset(vis,0,sizeof(vis));
    memset(lx,0,sizeof(lx));
    memset(belong,0,sizeof(belong));
    memset(ly,0,sizeof(ly));
    for (i=1;i<=n;i++)
      for (j=1;j<=m;j++) lx[i]=max(lx[i],e[i][j]);
    for (ii=1;ii<=n;ii++)
    {
        for (j=1;j<=m;j++) delta[j]=INF;
        while (1)
        {
            id++;
            if (find(ii,id)) break;
            int a=INF;
            for (i=1;i<=m;i++) 
              if (vis[i+n]!=id) a=min(delta[i],a);//注意这里只有没访问过的才更新a
            for (i=1;i<=n;i++)
              if (vis[i]==id) lx[i]-=a;
            for (i=1;i<=m;i++)
              if (vis[i+n]==id) ly[i]+=a;
              else delta[i]-=a; 
        }
    }
    for (i=1;i<=m;i++) 
      if (belong[i]) ans+=e[belong[i]][i];
    return ans;
}
int main()
{
    while (scanf("%d%d",&n,&m)!=EOF)
    {
        int use=0,i,j;
        for (i=1;i<=n;i++)
          for (j=1;j<=m;j++) scanf("%d",&e[i][j]),e[i][j]*=100;
        for (i=1;i<=n;i++)
        {
            int x;scanf("%d",&x);
            use+=e[i][x]/100; e[i][x]++;
        }
        int ans=KM();
        printf("%d %d\n",n-ans%100,ans/100-use);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值