Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 16870 | Accepted: 8636 |
Description
Input
Output
Sample Input
2 2 .m H. 5 5 HH..m ..... ..... ..... mm..H 7 8 ...H.... ...H.... ...H.... mmmHmmmm ...H.... ...H.... ...H.... 0 0
Sample Output
2 10 28
思路:
最小费用最大流初学,模板题;
可以说是利用EK算法来确定最大流,利用SPFA算法判断存不存在可行流,并选择出最小费用的可行流;两者结合即可求出最小费用最大流;
也可用最大权二分匹配KM算法解决,但须注意,这儿求的是最小花费,所以用KM时要把权值 * -1,最后再把结果 * -1即可,也当做模板题整理!
最小费用最大流代码:
#include <stdio.h> #include <string.h> #include <math.h> #include <queue> #define INF 0x7fffffff #define EN 200000 #define N 300 using namespace std; struct Edge{ int u, v; // 起点,终点 int w, c; // 代价,流量 int next; // 类似单链表中的next指针 }; struct Node{ int x; int y; }; Edge edge[EN]; // 边 Node H[N], M[N]; int ep = 0; bool vis[N]; int pre[N]; // 保存i的父节点 int min_weight[N]; // 原SPFA最小距离(dis)数组,此处保存最小代价 int head[N]; // 临界表 void AddEdge(int u, int v, int w, int c) { edge[ep].u = u; // 建立正向边 edge[ep].v = v; edge[ep].w = w; edge[ep].c = c; edge[ep].next = head[u]; head[u] = ep ++; edge[ep].u = v; // 建立反向边 edge[ep].v = u; edge[ep].w = -w; edge[ep].c = 0; edge[ep].next = head[v]; head[v] = ep ++; } bool SPFA(int src, int des) // 根据流量求最小代价 { queue<int>q; memset(vis, 0, sizeof(vis)); memset(pre, -1, sizeof(pre)); for(int j = 0; j <= des; j ++){ min_weight[j] = INF; } q.push(src); vis[src] = 1; min_weight[src] = 0; while(!q.empty()){ int cur = q.front(); vis[cur] = 0; q.pop(); for(int i = head[cur]; i != -1; i = edge[i].next){ int v = edge[i].v; if(edge[i].c > 0 && min_weight[cur] + edge[i].w < min_weight[v]){ // 必须有流量,并且代价更小 min_weight[v] = min_weight[cur] + edge[i].w; pre[v] = i; if(!vis[v]){ vis[v] = 1; q.push(v); } } } } if(min_weight[des] == INF) return 0; else return 1; } int MCMF(int src, int des) // EK 最大流 { int ans = 0; while(SPFA(src, des)){ int i, min_c = INF; // 最小流量 /* for(i = des; i != src; i = edge[pre[i]].u){ if(min_c > edge[pre[i]].c){ min_c = edge[pre[i]].c; } } */ min_c = 1; // 此题最小流量一定是1 ans += min_weight[des] * min_c; for(i = des; i != src; i = edge[pre[i]].u){ edge[ pre[i] ].c -= min_c; edge[ pre[i]^1 ].c += min_c; // 反向边与正向变是挨着的,所以此处异或即可 } } return ans; } int main() { char c; int i, j; int row, col; while(scanf("%d%d", &row, &col), row && col){ int H_Num = 0, M_Num = 0; memset(head, -1, sizeof(head)); // 初始化临界表 for(i = 0; i < row; i ++){ for(j = 0; j < col; j ++){ scanf(" %c", &c); if(c == 'H'){ H_Num ++; H[H_Num].x = i; H[H_Num].y = j; } else if(c == 'm'){ M_Num ++; M[M_Num].x = i; M[M_Num].y = j; } } } for(i = 1; i <= M_Num; i ++){ // 建图 for(j = 1; j <= H_Num; j ++){ int mh_min_weight = abs(M[i].x - H[j].x) + abs(M[i].y - H[j].y); AddEdge(i, M_Num + j, mh_min_weight, 1); } } int src = 0; // 超级源点 int des = M_Num + H_Num + 1; // 超级汇点 for(i = 1; i <= M_Num; i ++) // 将超级源点和超级汇点加入到图 AddEdge(src, i, 0, 1); for(i = M_Num + 1; i <= M_Num + H_Num; i ++) AddEdge(i, des, 0, 1); int ans = MCMF(src, des); printf("%d\n", ans); } return 0; }
KM算法代码:
#include <stdio.h> #include <string.h> #include <math.h> #define INF 0x7fffffff #define N 120 struct Node{ int x; int y; }; int nx, ny; int w[N][N]; // 保存权值 int lx[N], ly[N]; // 可行顶标 int pre[N], slack[N]; // 父节点,保存修改量 bool visx[N], visy[N]; bool DFS(int x) { visx[x] = 1; for(int y = 0; y < ny; y ++){ if(visy[y]){ continue; } int t = lx[x] + ly[y] - w[x][y]; if(t == 0){ // 相等子图 visy[y] = 1; if(pre[y] == -1 || DFS(pre[y]) ){ pre[y] = x; return 1; // 找到增广路 } } else if(slack[y] > t){ // 不在相等子图中slack 取最小的 slack[y] = t; } } return 0; // 未找到增广路 } int KM() { int i, j; memset(pre, -1, sizeof(pre)); memset(ly, 0, sizeof(ly)); for(i = 0; i < nx; i ++){ lx[i] = -INF; for(j = 1; j < ny; j ++){ if(w[i][j] > lx[i]){ lx[i] = w[i][j]; } } } for(int x = 0; x < nx; x ++){ for(i = 0; i < ny; i ++){ slack[i] = INF; } while(1){ memset(visx, 0, sizeof(visx)); memset(visy, 0, sizeof(visy)); if(DFS(x)){ // 找到增广路,为下一点增广,否则修改顶标 break; } int d = INF; for(i = 0; i < ny; i ++){ if(!visy[i] && d > slack[i]) d = slack[i]; } for(i = 0; i < nx; i ++){ if(visx[i]){ lx[i] -= d; } } for(i = 0; i < ny; i ++){ if(visy[i]){ ly[i] += d; } else{ slack[i] -= d; // 修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d } } } } int ans = 0; for(i = 0; i < ny; i ++){ if(pre[i] >= 0) ans += w[pre[i]][i]; } return ans; } int main() { char c; int x, y; Node H[N], M[N]; while(scanf("%d%d", &x, &y), x && y){ memset(w, 0, sizeof(w)); nx = ny = 0; int i, j; for(i = 0; i < x; i ++){ for(j = 0; j < y; j ++){ scanf(" %c", &c); if(c == 'H'){ H[nx].x = i; H[nx].y = j; nx ++; } else if(c == 'm'){ M[ny].x = i; M[ny].y = j; ny ++; } } } int dis; for(i = 0; i < nx; i ++){ // 建图 for(j = 0; j < ny; j ++){ dis = abs(H[i].x - M[j].x) + abs(H[i].y - M[j].y); w[i][j] = - dis; } } int ans = KM(); printf("%d\n", -ans); } return 0; }