Assignment
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1255 Accepted Submission(s): 655
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.
1<=N<=M<=50, 1<Eij<=10000.
Your program should process to the end of file.
3 3 2 1 3 3 2 4 1 26 2 2 1 3 2 3 1 2 3 1 2 3 1 2
2 26 1 2
这题运用的kM模板不难,主要考察的是如何解决最少改变原匹配数,对思维的要求很高
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 201;
int lx[N], ly[N], visx[N], visy[N], match[N], wight[N][N], slack[N], val[N], visit[N][N];
int dfs(int x);
int n, m;
const int inf = 0x3f3f3f3f;
void km();
int main()
{
while(scanf("%d %d", &n, &m)!=EOF)
{
memset(wight,0,sizeof(wight));
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
scanf("%d",&wight[i][j]);
}
}
memset(match,-1,sizeof(match));
memset(visit,0,sizeof(visit));
int sum=0, sum1=0;
for(int i=1; i<=n; i++)
{
int v;
scanf("%d", &v);
sum1+=wight[i][v];
visit[i][v]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(visit[i][j])
{
wight[i][j]=wight[i][j]*(n+1)+1;
}
else
{
wight[i][j]=wight[i][j]*(n+1);
}
}
}
km();
for(int i=1;i<=m;i++)
{
if(match[i]!=-1)
{
sum+=wight[match[i]][i];
}
}
printf("%d %d\n",n-sum%(n+1),sum/(n+1)-sum1);
}
return 0;
}
void km()
{
memset(ly,0,sizeof(ly));
for(int i=1; i<=n; i++)
{
lx[i]= -inf;
for(int j=1; j<=m; j++)
{
lx[i]=max(wight[i][j],lx[i]);
}
}
for(int i=1; i<=n; i++)
{
memset(slack,0x3f3f,sizeof(slack));
while(1)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfs(i))
{
break;
}
else
{
int tmp=inf;
for(int j=1; j<=m; j++)
{
if(!visy[j])
{
tmp=min(tmp,slack[j]);
}
}
for(int j=1; j<=m; j++)
{
if(visx[j])
{
lx[j]-=tmp;
}
if(visy[j])
{
ly[j]+=tmp;
}
else
{
slack[j]-=tmp;
}
}
}
}
}
}
int dfs(int x)
{
visx[x]=1;
for(int i=1; i<=m; i++)
{
if(visy[i])
{
continue;
}
if(lx[x]+ly[i]==wight[x][i])
{
visy[i]=1;
if(match[i]==-1||dfs(match[i]))
{
match[i]=x;
return 1;
}
}
else
{
slack[i]=min(slack[i],lx[x]+ly[i]-wight[x][i]);
}
}
return 0;
}
首先如果我们只需要求最大权值匹配,那么这道题是一个左右点集大小不对称的最优匹配问题。这里要注意KM算法模板的修改。
最优权值匹配很好求,直接用KM模板,但是要在原匹配边的基础上使得改变的边最少,这里我们这么处理:
这里由于左边点集有N个点,且M>=N。那么最终的最优匹配必然有N条边。我们让原图中的每条边的权值都乘以(N+1),即扩大N+1倍。且如果某条边本来就是原匹配用的其中一条边,那么该边权值在扩大N+1倍后,再加1。 所以任意一条边的权值只能是N+1的倍数 或 (N+1的倍数)+1,我们将要在这种权值的边中选出N条来(想想如果我们最终在新二分图求出的最优权值和==(N+1)的倍数,那么说明什么?说明我们最优匹配中,一条老边都没有复用.虽然老边的权值有加1的优势 )。
最终我们得到的最优权值和ans除以(N+1)就是最优权值解(因为就算我们原封不动的还用以前的匹配,也就是在所有权值的基础上加了N个1,此时/(N+1),整除归0)。
最终ans%(N+1)就是我们复用旧边的条数(上述两条结论仔细验证)。
下面来论证一下为什么上面的”所有边权值*(N+1),老边还+1”这个策略可行?
假设原图有唯一最优权值的解(即所有其他的匹配方案边的权值总和都要小于该解的权值和)。
那么”所有边权值*(N+1),老边还+1”之后.假设原先次优解的权值只比最优解的权值少1,
且次优解匹配用的都是原匹配边(即操作后次优解权值和增加的最多)
且最优解匹配用的都是原非匹配边(即最优解权值和增加的最少).
就算是这样,操作后,最优解的权值依然正好比次优解权值和大1.
所以如果最优解唯一,我们不会丢失最优解.
下面假设原图最优解方案不唯一,即有多个最优解方案可以得到最优解. 当时我们能肯定 那个用到老边最多的最优解方案 在我们”所有边权值*(N+1),老边还+1”操作后,其权值必然是所有解里面最大的. 即它变成了新的最优解,我们自然选它.
上述两段论证了为什么我们”所有边权值*(N+1),老边还+1”这个操作的可行性.