题目链接:http://poj.org/problem?id=2516
之前用费用流做过一次,挺恶心的,各种数组,然后今天又用km做了一遍,更恶心了,不过还好学到了点东西
//km算法中左边点集要少于右边,经过拆点后的商店数一定是少于等于供货点的,所以商店为第一点集,供货点为第二点集
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 100;
const int INF = 0x3f3f3f3f;
int n, m, k;
int need1[N], need2[N];
int dian[N][N], supply[N][N];
int mpa[N][N][N];
int s[N*10][N*10], match[N*10];
int nx, ny;
int lx[N*10], ly[N*10], slack[N*10];
bool visx[N*10], visy[N*10];
bool hungary(int v)
{
visx[v] = true;
for(int i = 0; i < ny; i++)
{
if(visy[i]) continue;
if(lx[v] + ly[i] == s[v][i])
{
visy[i] = true;
if(match[i] == -1 || hungary(match[i]))
{
match[i] = v;
return true;
}
}
else slack[i] = min(slack[i], lx[v] + ly[i] - s[v][i]);
}
return false;
}
void km()
{
memset(ly, 0, sizeof ly);
for(int i = 0; i < nx; i++)
lx[i] = -INF;
for(int i = 0; i < nx; i++)
for(int j = 0; j < ny; j++)
lx[i] = max(lx[i], s[i][j]);
for(int i = 0; i < nx; i++)
{
memset(slack, 0x3f, sizeof slack);
while(true)
{
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
if(hungary(i)) break;
else
{
int tmp = INF;
for(int j = 0; j < ny; j++)
if(! visy[j]) tmp = min(tmp, slack[j]);
for(int j = 0; j < nx; j++)
if(visx[j]) lx[j] -= tmp;
for(int j = 0; j < ny; j++)
if(visy[j]) ly[j] += tmp;
else slack[j] -= tmp;
}
}
}
}
int main()
{
while(scanf("%d%d%d", &n, &m, &k), n || m || k)
{
memset(need1, 0, sizeof need1);
memset(need2, 0, sizeof need2);
for(int i = 0; i < n; i++)
for(int j = 0; j < k; j++)
{
scanf("%d", &dian[i][j]);
need1[j] += dian[i][j];
}
for(int i = 0; i < m; i++)
for(int j = 0; j < k; j++)
{
scanf("%d", &supply[i][j]);
need2[j] += supply[i][j];
}
bool f = true;
for(int i = 0; i < k; i++)
if(need1[i] > need2[i])
{
f = false;
break;
}
for(int i = 0; i < k; i++)
for(int j = 0; j < n; j++)
for(int l = 0; l < m; l++)
scanf("%d", &mpa[i][j][l]);
if(f == false)
{
printf("-1\n");
continue;
}
int res = 0;
for(int i = 0; i < k; i++)
{
memset(match, -1, sizeof match);
int tp1[N*10], tp2[N*10];
nx = 0, ny = 0;
//拆点,因为km建图连边时无法表示数量,所以要把所有点拆成一个一个的,方式有点小技巧
for(int j = 0; j < n; j++)
for(int l = 0; l < dian[j][i]; l++)
tp1[nx++] = j;
for(int j = 0; j < m; j++)
for(int l = 0; l < supply[j][i]; l++)
tp2[ny++] = j;
for(int j = 0; j < nx; j++)
for(int l = 0; l < ny; l++)
s[j][l] = -mpa[i][tp1[j]][tp2[l]];
km();
for(int j = 0; j < ny; j++)
if(match[j] != -1)
res += s[match[j]][j];
}
printf("%d\n", -res);
}
return 0;
}