| Time Limit: 1000MS | Memory Limit: 65536K | |
| Total Submissions: 16870 | Accepted: 8636 |
Description
Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a '.' means an empty space, an 'H' represents a house on that point, and am 'm' indicates there is a little man on that point.

You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.
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;
}
2106



被折叠的 条评论
为什么被折叠?



