HDU 2853-Assignment 二分图最优匹配(优先保留原来的边)

Assignment

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1657    Accepted Submission(s): 871


 

Problem Description

Last year a terrible earthquake attacked Sichuan province. About 300,000 PLA soldiers attended the rescue, also ALPCs. Our mission is to solve difficulty problems to optimization the assignment of troops. The assignment is measure by efficiency, which is an integer, and the larger the better.
We have N companies of troops and M missions, M>=N. One company can get only one mission. One mission can be assigned to only one company. If company i takes mission j, we can get efficiency Eij.
We have a assignment plan already, and now we want to change some companies’ missions to make the total efficiency larger. And also we want to change as less companies as possible.

 

 

Input

For each test case, the first line contains two numbers N and M. N lines follow. Each contains M integers, representing Eij. The next line contains N integers. The first one represents the mission number that company 1 takes, and so on.
1<=N<=M<=50, 1<Eij<=10000.
Your program should process to the end of file.

 

 

Output

For each the case print two integers X and Y. X represents the number of companies whose mission had been changed. Y represents the maximum total efficiency can be increased after changing.

 

 

Sample Input

3 3

2 1 3

3 2 4

1 26 2

2 1 3

2 3

1 2 3

1 2 3

1 2

 

 

Sample Output

2 26

1 2

 


题意:

      有n个公司,m个任务,现在给你每个公司完成每一个任务都会获得的相应的利润,以及原来计划的每个公司要做的任务。你要求出每个公司得到相应任务的最大值,以及得到该最大值最少需要改变多少条边(即优先保留原来的边)。

做法:

     如果没有优先保留原来的边这个限制,那么这道题就是一道裸的KM模板,但是由于这个条件,这道题的处理就会变得特殊。如何处理呢。首先,我们把每一条边都扩大n+1倍,然后再把原来的边都+1,跑一边最大匹配之后,ans%(n+1)就是保留的原先的边的数量,ans/(n+1)就是最后匹配到的最大利润。

     为什么要这么做处理呢,首先因为要优先保留原来的边,即再遇到权值相同的边的时候要优先选原来的边,我们就可以把原来的权值全部乘上一个比较大的数,然后再在原边上+1,这样就能保证优先选择原来的边,但是为什么要乘n+1呢,因为我们匹配到的数量最多(这道题是肯定)是n条,所以乘上n+1之后最后的答案最多也是   oldans(原先未乘的答案)*(n+1)+n,这样再把这个答案除上(n+1)之后对原来的答案不会产生影响,而且这样处理之后对新的ans取余,得到的就是选择的旧边的数量,一举多得,是个很好的方法。


 代码如下:

   

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=55;
const int qwq=0x7fffffff;
int w[maxn][maxn];  //w数组记录边权值
int line[maxn],usex[maxn],usey[maxn],cx[maxn],cy[maxn];  //line数组记录右边端点所连的左端点, usex,usey数组记录是否曾访问过,也是判断是否在增广路上,cx,cy数组就是记录点的顶标
int n,ans,m;  //n左m右
bool find(int x){
    usex[x]=1;
    for (int i=1;i<=m;i++){
        if ((usey[i]==0)&&(cx[x]+cy[i]==w[x][i])){   //如果这个点未访问过并且它是子图里面的边
            usey[i]=1;
            if ((line[i]==0)||find(line[i])){   //如果这个点未匹配或者匹配点能更改
                line[i]=x;
                return true;
            }
        }
    }
    return false;
}
int km(){
    for (int i=1;i<=n;i++){  //分别对左边点依次匹配
        while (true){
            int d=qwq;
            memset(usex,0,sizeof(usex));
            memset(usey,0,sizeof(usey));
            if (find(i)) break;  //直到成功匹配才换下一个点匹配
            for (int j=1;j<=n;j++){
                if (usex[j]){
                    for (int k=1;k<=m;k++)
                    if (!usey[k]) d=min(d,cx[j]+cy[k]-w[j][k]);  //计算d值
                }
            }
            if (d==qwq) return -1;
            for (int j=1;j<=n;j++)
                if (usex[j]) cx[j]-=d;
            for (int j=1;j<=m;j++)
                if (usey[j]) cy[j]+=d;     //添加新边
        }
    }
    ans=0;
    for (int i=1;i<=m;i++)
    ans+=w[line[i]][i];
    return ans;
}
int main(){
    while (~scanf("%d%d",&n,&m)){
        memset(cy,0,sizeof(cy));
        memset(w,0,sizeof(w));
        memset(cx,0,sizeof(cx));
        for (int i=1;i<=n;i++){
            for (int j=1;j<=m;j++){
                scanf("%d",&w[i][j]);
                w[i][j]*=(n+1);
            }
        }
        int x,initans=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            w[i][x]++;
            initans+=w[i][x]/(n+1);
        }
        for(int i=1;i<=n;i++){
            int d=w[i][1];
            for (int j=1;j<=m;j++){
                d=max(d,w[i][j]);
            }
            cx[i]=d;
        }
        memset(line,0,sizeof(line));
        int ans=km();

        printf("%d %d\n",n-ans%(n+1),ans/(n+1)-initans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值