传送门:
https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2378
翻译
在一个N*N的方格中,各有一个整数w(i,j),现在要求给每行构造row(i),给每列构造col(j),使得任意w(i,j)<=row(i)+col(j),输出row(i)与col(j)之和最小的方案。
没什么好说的。。。
上来一看题觉得挺唬人,不过仔细一看这个玩意跟我们跑带权二分图KM算法时的要求一模一样。row和col不就是我们X部和Y部的lx跟ly吗?正好我们KM算法求的ans本身就是最优匹配的最小值,跟这道题给的一模一样。所以虽然这道题跟二分图没什么关系,但直接套一个KM的板子即为正解。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
#define maxn 510
#define maxm 250010
#define INF 0x3f3f3f3f
struct node{
int to,val,next;
}e[maxm];int len;
int head[maxn];
void Add(int x,int y,int c){
e[++len].to=y;
e[len].val=c;
e[len].next=head[x];
head[x]=len;
}
int a[maxn][maxn];
int n;
int lx[maxn],ly[maxn],match[maxn],slack[maxn];
bool visx[maxn],visy[maxn];
bool Find(int x){
visx[x]=1;
for(int i=head[x];i;i=e[i].next)
if(!visy[e[i].to]){
int y=e[i].to;
if(e[i].val==lx[x]+ly[y]){
visy[y]=1;
if(!match[y]||Find(match[y])){
match[y]=x;
return 1;
}
}
else slack[y]=min(slack[y],lx[x]+ly[y]-e[i].val);
}
return 0;
}
void KM(){
memset(match,0,sizeof(match));
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=e[j].next)
lx[i]=max(lx[i],e[j].val);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
slack[j]=INF;
while(1){
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(Find(i)) break;
int delta=INF;
for(int j=1;j<=n;j++){
if(!visy[j]){
delta=min(delta,slack[j]);
}
}
if(delta==INF) return;
for(int j=1;j<=n;j++)
{
if(visx[j]) lx[j]-=delta;
if(visy[j]) ly[j]+=delta;
else slack[j]-=delta;
}
}
}
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
len=0;
memset(head,0,sizeof head);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
int x;
scanf("%d",&x);
Add(i,j,x);
}
KM();
for(int i=1;i<=n;i++) printf("%d ",lx[i]);
printf("
");
for(int i=1;i<=n;i++) printf("%d ",ly[i]);
printf("
");
int ans=0;
for(int i=1;i<=n;i++) ans+=lx[i]+ly[i];
printf("%d
",ans);
}
return 0;
}