说明:此题思路代码为网上他人思路与代码,忘记出处,故而抄袭与此。
题意:给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。
参考:MCMF模版题/KM模板题
解题思路:
费用流问题。
构图:
把man作为一个顶点集合U,house作为另一个顶点集合V,把U中所有点到V中所有点连线,费用cost[u][v]为abs(△x)+abs(△y),反向弧费用cost[v][u]= -cost[u][v],容量cap[u][v]=1,构成一个多源多汇的二分图。
由于每一个多源多汇的网络流都必有一个与之对应的单源单汇的网络流,为了便于解题,由此构造一个超级源s和超级汇t,超级源s与U中所有点相连,费用cost[s][u]=0(这是显然的),容量cap[s][u]=1;V中所有点与超级汇t相连,费用cost[v][t]=0(这是显然的),容量cap[t][v]=1。
至于其他不连通的点,费用与容量均为0。容量为0的边,可以理解为饱和边,不再连通。而上述的所有边之所以容量初始化为1,是因为每间house只允许入住1个man。而与超级源(汇)相连的边的费用之所以为0,是为了现在所构造的单源单汇网络流最终所求的最小费用等于原来的多源多汇网络流的最小费用。
求解:
接下来的解题方法有关“最小费用最大流”,请未知的同学先去看看相关文献。
其实题目所求的最小费用,就是最短距离。用spfa算法求上述二分图G的最短路径,该最短路径就是图G的所有增广链中费用最小的一条。比较该增广链上所有边的容量,最小的容量就是“可分配的最大流MaxFlow”。
再者就是利用MaxFlow对增广链上的各条边的容量进行调整,正向弧容量减去MaxFlow,反向弧容量加上MaxFlow。然后该条增广链上各条边的费用分别乘以MaxFlow之和,就是第一个man到达合适的house所花费的最小费用。而图G经过调整,变成图G1。
针对图G1再次使用spfa算法,找到第二条增广链.....重复上述算法,直到无法找到增广链为止,则得到的费用和就是所求。
此种解题方法是先计算最小费用,然后再在保证费用最小的情况下,增大流量,最终达到求解目的。理论上,对于有n个man的图,可以找到n条增广链,即重复迭代spfa算法n次。若超过n次,则说明该图存在负权环,无解。但本题并无输出“无解”的要求,故可认为测试数据中不存在此类数据,因此在循环spfa算法时,不必计算次数,用while()足矣。
最后要注意的是:
Discuss上很多人说用了栈空间定义的数组,提交会RE或TLE,数组开大之后才能AC,因此便怪罪于测试数据有误。
其实不然。真正的原因是,题目所提及的N与M纯粹是地图的行数和列数,与man或house的个数无关,而我看了他们的代码,有部分同学却误以为N就是man的个数,导致出现RE。
因此在此强调一下,题目的N不等于man的个数,建议大家先对地图的man个数进行数数,得到man的个数,然后再开堆空间(new函数);而非要开栈空间的同学也未尝不可,由于N与M的值均<=100,且man与house的个数一致,则认为man的个数上限为100*100/2=5000。
MCMF解法:
#include<stdio.h>
#include<string.h>
#include<queue>
#include<math.h>
#include<iostream>
using namespace std;
#define inf 0xfffffff
#define M 750
struct node {
int x, y;
};
int mat[M][M];//记录边的容量
char str[M][M];
int tem[M][M];
int n, m, len;
struct edge {
int v, w, flow, c, next;
} edge[M * M * 10];
int vst[M], dis[M], head[M], pre[M];//dis记录路径长度
int tot, flow_sum;
int tabs(int x) {
if (x < 0) return -x;
return x;
}
void _add(int v, int w, int f, int c) {
edge[tot].v = v;
edge[tot].w = w;
edge[tot].flow = f;
edge[tot].c = c;
edge[tot].next = head[v];
head[v] = tot++;
}
void add(int v, int w, int f, int c) {
_add(v, w, f, c);
_add(w, v, 0, -c);
}
//MCMF开始
bool spfa(int begin, int end) {//spfa求解最短路径,即增广链中最小费用
int v, w;
queue < int >q;
for (int i = 0; i <= end + 2; i++) {
pre[i] = -1;
vst[i] = 0;
dis[i] = inf;
}
vst[begin] = 1;
dis[begin] = 0;
q.push(begin);
while (!q.empty()) {
v = q.front();
q.pop();
vst[v] = false;
for (int i = head[v]; i != -1; i = edge[i].next) {
if (edge[i].flow > 0) {
w = edge[i].w;
if (dis[w] > dis[v] + edge[i].c) {
dis[w] = dis[v] + edge[i].c;
pre[w] = i;
if (!vst[w]) {
vst[w] = true;
q.push(w);
}
}
}
}
}
return dis[end] != inf;
}
int MCMF(int begin, int end) {
int ans = 0, flow, i;
flow_sum = 0;
while (spfa(begin, end)) {
flow = inf;
for (i = pre[end]; i != -1; i = pre[edge[i].v])
if (edge[i].flow < flow)
flow = edge[i].flow;//找增广链上所有边的容量的最小值作为“可分配最大流”
for (i = pre[end]; i != -1; i = pre[edge[i].v]) {
edge[i].flow -= flow; //正向弧加
edge[i^1].flow += flow; //反向弧减
}
ans += dis[end];
flow_sum += flow; //总流量
}
// cout << flow_sum << endl;
return ans; // 返回最小费用
}
void transt() {
int i, j;
queue<node> Q;
memset(tem, -1, sizeof (tem));
int e = 0;
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
if (str[i][j] == 'm') {
node t;
t.x = i;
t.y = j;
Q.push(t);
} else if (str[i][j] == 'H') {
tem[i][j] = (++e);//tem记录房子的编号
}
}
}
int no = e;
while (!Q.empty()) {
node k = Q.front();
Q.pop();
no++;//no++相当于在房子编号完的基础上继续给人编号
for (i = 0; i < n; i++) {
for (j = 0; j < m; j++) {
if (tem[i][j] != -1) {
mat[no][tem[i][j]] = tabs(k.x - i) + tabs(k.y - j);//人与各个房子之间的距离
}
}
}
}
len = no;//len为整个链的长度
}
int main() {
int i, j;
while (scanf("%d%d", &n, &m) != EOF) {
if (n == 0 && m == 0) break;
memset(str, 0, sizeof (str));
for (i = 0; i < n; i++) {
scanf("%s", str[i]);
}
memset(mat, -1, sizeof (mat));
transt();
tot = 0;
memset(head, -1, sizeof (head));
for (i = len / 2 + 1; i <= len; i++) {
for (j = 1; j <= len / 2; j++) {
add(i, j, 1, mat[i][j]);
}
}
for (i = len / 2 + 1; i <= len; i++)
add(0, i, 1, 0);
for (i = 1; i <= len / 2; i++)
add(i, len + 1, 1, 0);
cout << MCMF(0, len + 1) << endl;
}
return 0;
}
KM解法(不懂,先留着代码):
//KM copy其他大牛代码。。。
#include <iostream>
#include<string.h>
#include <math.h>
#include <climits>
using namespace std;
struct Point {
int x;
int y;
} man[10001], home[10001];
int n;
int m;
int map[101][101];
int lx[101];
int ly[101];
int match[101];
int visx[101];
int visy[101];
char c;
int mNum; //人的个数
int hNum; //房子的个数
bool dfs(int a) {
int tmp;
visx[a] = 1;
for (int i = 0; i < hNum; i++) {
if (visy[i] != 1 && map[a][i] == lx[a] + ly[i]) {
tmp = match[i];
visy[i] = 1;
match[i] = a;
if (tmp == -1 || dfs(tmp)) {
return true;
}
match[i] = tmp;
}
}
return false;
}
void solve() {
for (int i = 0; i < mNum; i++) {
while (true) {
memset(visx, 0, sizeof (visx));
memset(visy, 0, sizeof (visy));
int min = INT_MAX;
if (dfs(i)) {
break; //如果找到完全匹配,则跳出
}
for (int j = 0; j < mNum; j++) {
if (visx[j]) {
for (int k = 0; k < hNum; k++) {
if (!visy[k] && min > map[j][k] - lx[j] - ly[k]) {
min = map[j][k] - lx[j] - ly[k];
}
}
}
}
for (int j = 0; j < mNum; j++) {
if (visx[j]) {
lx[j] += min; //这里与前面的思想不是太一样,为什么呢??
}
}
for (int j = 0; j < hNum; j++) {
if (visy[j]) {
ly[j] -= min;
}
}
}
}
}
int main() {
while (true) {
cin >> n >> m;
if (n == 0 && m == 0) {
return 0;
}
mNum = 0;
hNum = 0;
memset(map, 0, sizeof (map));
//这里是建图的过程,先把m H点的坐标记下,之后计算之间的距离,存在map中
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> c;
if (c == 'm') {
man[mNum].x = i;
man[mNum].y = j;
mNum++;
} else {
if (c == 'H') {
home[hNum].x = i;
home[hNum].y = j;
hNum++;
}
}
}
}
for (int i = 0; i < mNum; i++) {
for (int j = 0; j < hNum; j++) {
map[i][j] = (int)fabs(double(man[i].x - home[j].x)) + (int)fabs(double(man[i].y
- home[j].y));
}
}
memset(lx, 127, sizeof (lx));
memset(ly, 0, sizeof (ly));
for (int i = 0; i < mNum; i++) {
for (int j = 0; j < hNum; j++) {
if (map[i][j] < lx[i]) {
lx[i] = map[i][j]; //lx[i]取能和man[i]匹配的最小值
}
}
}
memset(match, -1, sizeof (match));
solve();
int sum = 0;
for (int i = 0; i < hNum; i++) {
sum += map[match[i]][i];
}
cout << sum << endl;
}
return 0;
}