D - Going Home POJ - 2195
- 题目链接:http://poj.org/problem?id=2195
- 题意:给你一个N行M列的方格图,里面人的个数H, 和房子的个数m,每个人都可以往上下左右的相邻方格走,每走一步,你需要支付一美元,每个人都要到一个房子里去,一个房子只能住一个人,人可以直接经过房子,而不需要绕路。问每个人都进房子的最小花费。
- 思路:
- 这是一个最小费用最大流问题
- //最大费用最小流只要在添加边的时候换一下位置就好了
- //求最大费用最大流只需要把费用换成相反数,用最小费用最大流求解即可
- 建立超级源点,分别连接每个m,容量为1,费用0
- 建立超级汇点,分别把每个H连接到汇点,容量为1,费用为0
- 再把每个m分别指向H,容量为1,费用为该m到H的横纵坐标之差的绝对值的和,|X1-X2|+|Y1-Y2|;
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <math.h>
#define pi acos(-1)
#define fastio ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
const int INF = 0x3f3f3f3f;
const LL ll_INF = 0x3f3f3f3f3f3f3f3f;
const int maxn = 200 + 10;
const int Max_row = 110, Max_col = 110, Max_v = 110*3, Max_e = 110*110+110*3;
char Grid[Max_row][Max_col];
PII Vertex[Max_v];
int tot=0;
int tol;
int head[Max_e];
int st=0,en=0;
int dis[maxn*maxn];
int vis[maxn*maxn];
int pre[maxn*maxn];//记录增广路径上 到达点i的边的编号
//最小费用最大流模版.求最大费用最大流建图时把费用取负即可。
//无向边转换成有向边时需要拆分成两条有向边。即两次加边。
struct Edge
{
int u;
int v;
int cap;
int cost;
int next;
}edge[maxn*maxn + maxn*3];
void init()
{
memset(head,-1,sizeof(head));
tol=0;
tot=0;
}
void add_edge(int u,int v,int cap,int cost)
{
edge[tol].u=u;edge[tol].v=v;edge[tol].cap=cap;
edge[tol].cost=cost;edge[tol].next=head[u];
head[u]=tol++;
edge[tol].u=v;edge[tol].v=u;edge[tol].cap=0;
edge[tol].cost=-cost;edge[tol].next=head[v];
head[v]=tol++;
}
bool spfa(int s,int t ,int n)//0表示没有增广路 //寻找花销最少的路径
{
//跑一遍SPFA 找s——t的最少花销路径 且该路径上每一条边不能满流
//若存在 说明可以继续增广,反之不能
for(int i=0;i<=n;i++)
{
dis[i]=INF;
vis[i]=0;
pre[i]=-1;
}
dis[s]=0;
vis[s]=1;
queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();
Q.pop();
vis[u]=0;
for(int k=head[u];k!=-1;k=edge[k].next)
{
int v=edge[k].v;
int cost=edge[k].cost;
if(edge[k].cap && dis[v]> dis[u]+cost) //可以松弛 且 没有满流
{
dis[v]=dis[u]+cost;
pre[v]=k; //记录前驱边 的编号
if(!vis[v])
{
vis[v]=1;
Q.push(v);
}
}
}
}
if(dis[t]==INF)return 0;
return 1;
}
int MCMF(int s,int t,int n)
{
int minflow;//总流量
int mincost=0;//总费用
while(spfa(s,t,n))//每次寻找花销最小的路径
{
minflow=INF;//通过反向弧 在源点到汇点的最少花费路径 找最小增广流
for(int k=pre[t];k!=-1;k=pre[edge[k].u])
{
minflow=min(minflow,edge[k].cap);
}
//增广
for(int k=pre[t];k!=-1;k=pre[edge[k].u])
{
edge[k].cap-=minflow;
edge[k^1].cap+=minflow;//增广流的花销
}
mincost+=dis[t];
}
return mincost;
}
int main()
{
int N, M;
while(~scanf("%d%d", &N, &M)){
init();
if(N==0 && M==0) break;
for(int i=1; i<=N; i++){
scanf("%s", Grid[i]+1);
}
for(int i=1; i<=N; i++){
for(int j=1; j<=M; j++){
if(Grid[i][j] == 'm') Vertex[++tot] = PII(i, j);
}
}
int sz = tot;
for(int i=1; i<=N; i++){
for(int j=1; j<=M; j++){
if(Grid[i][j] == 'H') Vertex[++tot] = PII(i, j);// Vertex[sz+tot] = PII(i, j);
}
}
st = 0; en = 2*sz+1;
for(int i=1; i<=sz; i++){
add_edge(st, i, 1, 0);
for(int j=1; j<=sz; j++){
add_edge(i, sz+j, 1, abs(Vertex[i].first-Vertex[sz+j].first)+abs(Vertex[i].second-Vertex[sz+j].second));
}
//add_edge(sz+i, 2*sz+i, 1, 0);
add_edge(sz+i, en, 1, 0);
}
printf("%d\n", MCMF(st, en, en));
}
}