AssignmentTime 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.
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.
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;
}