题目大意:在一个网格里面有n个小男人和n个房子,现在想让每个小男人都有一个房子住,不过每个人移动一下都需要花费¥1,现在求出来最小的总花费。ps:可以认为网格的每个点都是很大的广场并且容纳所有的人,人可以走在有房子的点但是不进入房子。
分析:人-房子,很完美的带全都最小值匹配啊,人到一个房子的花费就是他们之间的曼哈顿距离,用这些距离构造一个二分图,然后用KM算法求出来最小费。下面是KM算法
***********************************************************************************************************************************
#include<stdio.h>
#include<string.h>
#include<queue>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN = 107;
const int oo = 1e9+7;
struct point{int x, y;}man[MAXN], house[MAXN];
char G[MAXN][MAXN];
int w[MAXN][MAXN], slack[MAXN], Nx, Ny;
int dx[MAXN], dy[MAXN], Ly[MAXN];
bool vx[MAXN], vy[MAXN];
void InIt()
{
Nx = Ny = 0;
for(int i=1; i<MAXN; i++)
{
dx[i] = -oo;
dy[i] = 0;
}
}
bool Find(int i)
{
vx[i] = true;
for(int j=1; j<=Ny; j++)
{
if(!vy[j] && w[i][j] == dx[i]+dy[j])
{
vy[j] = true;
if(!Ly[j] || Find(Ly[j]))
{
Ly[j] = i;
return true;
}
}
else if(!vy[j])
slack[j] = min(slack[j], dx[i]+dy[j]-w[i][j]);
}
return false;
}
int KM()
{
int i, j;
memset(Ly, 0, sizeof(Ly));
for(i=1; i<=Nx; i++)
{
for(j=1; j<=Ny; j++)
slack[j] = oo;
while(true)
{
memset(vx, false, sizeof(vx));
memset(vy, false, sizeof(vy));
if(Find(i) == true)
break;
int d = oo;
for(j=1; j<=Ny; j++)
{
if(!vy[j] && d > slack[j])
d = slack[j];
}
for(j=1; j<=Nx; j++)
{
if(vx[j])
dx[j] -= d;
}
for(j=1; j<=Ny; j++)
{
if(vy[j])
dy[j] += d;
else
slack[j] -= d;
}
}
}
int sum = 0;
for(i=1; i<=Ny; i++)
sum += w[Ly[i]][i];
return -sum;
}
int main()
{
int M, N;
while(scanf("%d%d", &M, &N), M+N)
{
int i, j;
InIt();
for(i=0; i<M; i++)
scanf("%s", G[i]);
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
if(G[i][j] == 'm')
{
Nx++;
man[Nx].x = i;
man[Nx].y = j;
}
if(G[i][j] == 'H')
{
Ny++;
house[Ny].x = i;
house[Ny].y = j;
}
}
for(i=1; i<=Nx; i++)
for(j=1; j<=Ny; j++)
{
w[i][j] = fabs(man[i].x-house[j].x) + fabs(man[i].y-house[j].y);
w[i][j] = -w[i][j];
dx[i] = max(dx[i], w[i][j]);
}
printf("%d ", KM());
}
return 0;
}
#include<string.h>
#include<queue>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN = 107;
const int oo = 1e9+7;
struct point{int x, y;}man[MAXN], house[MAXN];
char G[MAXN][MAXN];
int w[MAXN][MAXN], slack[MAXN], Nx, Ny;
int dx[MAXN], dy[MAXN], Ly[MAXN];
bool vx[MAXN], vy[MAXN];
void InIt()
{
Nx = Ny = 0;
for(int i=1; i<MAXN; i++)
{
dx[i] = -oo;
dy[i] = 0;
}
}
bool Find(int i)
{
vx[i] = true;
for(int j=1; j<=Ny; j++)
{
if(!vy[j] && w[i][j] == dx[i]+dy[j])
{
vy[j] = true;
if(!Ly[j] || Find(Ly[j]))
{
Ly[j] = i;
return true;
}
}
else if(!vy[j])
slack[j] = min(slack[j], dx[i]+dy[j]-w[i][j]);
}
return false;
}
int KM()
{
int i, j;
memset(Ly, 0, sizeof(Ly));
for(i=1; i<=Nx; i++)
{
for(j=1; j<=Ny; j++)
slack[j] = oo;
while(true)
{
memset(vx, false, sizeof(vx));
memset(vy, false, sizeof(vy));
if(Find(i) == true)
break;
int d = oo;
for(j=1; j<=Ny; j++)
{
if(!vy[j] && d > slack[j])
d = slack[j];
}
for(j=1; j<=Nx; j++)
{
if(vx[j])
dx[j] -= d;
}
for(j=1; j<=Ny; j++)
{
if(vy[j])
dy[j] += d;
else
slack[j] -= d;
}
}
}
int sum = 0;
for(i=1; i<=Ny; i++)
sum += w[Ly[i]][i];
return -sum;
}
int main()
{
int M, N;
while(scanf("%d%d", &M, &N), M+N)
{
int i, j;
InIt();
for(i=0; i<M; i++)
scanf("%s", G[i]);
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
if(G[i][j] == 'm')
{
Nx++;
man[Nx].x = i;
man[Nx].y = j;
}
if(G[i][j] == 'H')
{
Ny++;
house[Ny].x = i;
house[Ny].y = j;
}
}
for(i=1; i<=Nx; i++)
for(j=1; j<=Ny; j++)
{
w[i][j] = fabs(man[i].x-house[j].x) + fabs(man[i].y-house[j].y);
w[i][j] = -w[i][j];
dx[i] = max(dx[i], w[i][j]);
}
printf("%d ", KM());
}
return 0;
}
研究了一下网络网络流里面的最小费用最大流,听着名字比较高大上吧,看了半天终于恍然大悟,其实还是最大流。但是为什么又叫最小费用呢?所谓的最小费用并不是从源点到汇点的最小费用,而是再保证最大流的前提下的最小费用,我们求最大流的的办法就是不断进行路径增广,而怎么增广路径就比较随意了,可以用深搜或者广搜,不管怎么样只要找到可以进行增广的路就行,如果在这个前提下我们找增广路的时候用最短路的办法去找,那么岂不是找的每条路都是花费最小的,而且最后无路时候就是最小花费了(同时也求出来了最大流),有了这个认识就可以写最消费最大流了。
下面是用spfa(可以处理负权边)实现的。
***********************************************************************************************************************************
#include<stdio.h>
#include<string.h>
#include<queue>
#include<stack>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN = 407;
const int oo = 1e9+7;
struct point{int x, y;}man[MAXN], house[MAXN];
struct Graph{int flow, cost;}G[MAXN][MAXN];
int NX, NY, start, End;///男人和房子的数目,源点和汇点
bool spfa(int pre[])
{
stack<int> sta;
int instack[MAXN]={0}, dist[MAXN];
for(int i=1; i<=End; i++)
dist[i] = oo;
dist[start] = 0;
sta.push(start);
while(sta.size())
{
int u = sta.top();sta.pop();
instack[u] = false;
for(int i=1; i<=End; i++)
{
if(G[u][i].flow && dist[i] > dist[u]+G[u][i].cost)
{
dist[i] = dist[u] + G[u][i].cost;
pre[i] = u;
if(instack[i] == false)
{
sta.push(i);
instack[i] = true;
}
}
}
}
return dist[End] != oo;
}
int MinCost()
{
int i, pre[MAXN], cost=0;
while(spfa(pre) == true)
{///如果有增广路
int MinFlow = oo;
for(i=End; i != start; i=pre[i])
MinFlow = min(MinFlow, G[pre[i]][i].flow);
for(i=End; i != start; i=pre[i])
{///逆向访问这条增广路上的每条边
int k = pre[i];
G[k][i].flow -= MinFlow;
G[i][k].flow += MinFlow;
cost += G[k][i].cost;
}
}
return cost;
}
int main()
{
int M, N;
while(scanf("%d%d", &M, &N), M+N)
{
int i, j;char s[MAXN][MAXN];
memset(G, 0, sizeof(G));
NX = NY = 0;
for(i=0; i<M; i++)
scanf("%s", s[i]);
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
if(s[i][j] == 'm')
{
NX++;
man[NX].x = i;
man[NX].y = j;
}
if(s[i][j] == 'H')
{
NY++;
house[NY].x = i;
house[NY].y = j;
}
}
for(i=1; i<=NX; i++)
for(j=1; j<=NY; j++)
{///房子的编号从NX~NX+NY
G[i][NX+j].flow = 1;
G[i][NX+j].cost = fabs(man[i].x-house[j].x)+fabs(man[i].y-house[j].y);
G[NX+j][i].cost = -G[i][NX+j].cost;
}
start = NX+NY+1, End = start+1;
for(i=1; i<=NX; i++)
{///把源点与人连接
G[start][i].flow = 1;
G[start][i].cost = 0;
}
for(i=1; i<=NY; i++)
{///把房子和汇点连接
G[NX+i][End].flow = 1;
G[NX+i][End].cost = 0;
}
printf("%d ", MinCost());
}
return 0;
}
#include<string.h>
#include<queue>
#include<stack>
#include<algorithm>
#include<math.h>
using namespace std;
const int MAXN = 407;
const int oo = 1e9+7;
struct point{int x, y;}man[MAXN], house[MAXN];
struct Graph{int flow, cost;}G[MAXN][MAXN];
int NX, NY, start, End;///男人和房子的数目,源点和汇点
bool spfa(int pre[])
{
stack<int> sta;
int instack[MAXN]={0}, dist[MAXN];
for(int i=1; i<=End; i++)
dist[i] = oo;
dist[start] = 0;
sta.push(start);
while(sta.size())
{
int u = sta.top();sta.pop();
instack[u] = false;
for(int i=1; i<=End; i++)
{
if(G[u][i].flow && dist[i] > dist[u]+G[u][i].cost)
{
dist[i] = dist[u] + G[u][i].cost;
pre[i] = u;
if(instack[i] == false)
{
sta.push(i);
instack[i] = true;
}
}
}
}
return dist[End] != oo;
}
int MinCost()
{
int i, pre[MAXN], cost=0;
while(spfa(pre) == true)
{///如果有增广路
int MinFlow = oo;
for(i=End; i != start; i=pre[i])
MinFlow = min(MinFlow, G[pre[i]][i].flow);
for(i=End; i != start; i=pre[i])
{///逆向访问这条增广路上的每条边
int k = pre[i];
G[k][i].flow -= MinFlow;
G[i][k].flow += MinFlow;
cost += G[k][i].cost;
}
}
return cost;
}
int main()
{
int M, N;
while(scanf("%d%d", &M, &N), M+N)
{
int i, j;char s[MAXN][MAXN];
memset(G, 0, sizeof(G));
NX = NY = 0;
for(i=0; i<M; i++)
scanf("%s", s[i]);
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
if(s[i][j] == 'm')
{
NX++;
man[NX].x = i;
man[NX].y = j;
}
if(s[i][j] == 'H')
{
NY++;
house[NY].x = i;
house[NY].y = j;
}
}
for(i=1; i<=NX; i++)
for(j=1; j<=NY; j++)
{///房子的编号从NX~NX+NY
G[i][NX+j].flow = 1;
G[i][NX+j].cost = fabs(man[i].x-house[j].x)+fabs(man[i].y-house[j].y);
G[NX+j][i].cost = -G[i][NX+j].cost;
}
start = NX+NY+1, End = start+1;
for(i=1; i<=NX; i++)
{///把源点与人连接
G[start][i].flow = 1;
G[start][i].cost = 0;
}
for(i=1; i<=NY; i++)
{///把房子和汇点连接
G[NX+i][End].flow = 1;
G[NX+i][End].cost = 0;
}
printf("%d ", MinCost());
}
return 0;
}