poj 2195 Going Home (最小费用最大流)

这是一篇关于如何利用最小费用最大流解决POJ 2195 Going Home问题的文章。题目要求在地图上让每个man以最低费用入住对应的house。通过构建多源多汇的二分图并转化为单源单汇网络,使用SPFA算法寻找最短路径,不断调整边的容量和费用,直至找到所有man的合适house。在实际操作中,需迭代SPFA算法直到无法找到增广链,确保费用最小且所有man都有住处。
摘要由CSDN通过智能技术生成

大致题意:

给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离),一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。

 

解题思路:

费用流问题。

 

构图:

       把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()足矣。

#include <set>
#include <map>
#include <stack>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define L(i) i<<1
#define R(i) i<<1|1
#define INF  0x3f3f3f3f
#define pi acos(-1.0)
#define eps 1e-9
#define maxn 100100
#define MOD 1000000007

const int MAXN = 100010;
const int MAXM = 400010;
struct Edge
{
    int to,next,cap,flow,cost;
} edge[MAXM];
int tot,n,m,N;
int head[MAXN],vis[MAXN];
int pre[MAXN],dis[MAXN];

void init()
{
    N = n;
    tot = 0;
    memset(head,-1,sizeof(head));
}
void add_edge(int u,int v,int cap,int cost)
{
    edge[tot].to = v;
    edge[tot].cap = cap;
    edge[tot].flow = 0;
    edge[tot].cost = cost;
    edge[tot].next = head[u];
    head[u] = tot++;
    edge[tot].to = u;
    edge[tot].cap = 0;
    edge[tot].flow = 0;
    edge[tot].cost = -cost;
    edge[tot].next = head[v];
    head[v] = tot++;
}
int spfa(int s,int t)
{
    queue<int> q;
    for(int i = 0; i < N; i++)
    {
        dis[i] = INF;
        vis[i] = 0;
        pre[i] = -1;
    }
    dis[s] = 0;
    vis[s] = 1;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].to;
            if(edge[i].cap > edge[i].flow && dis[v] > dis[u] + edge[i].cost)
            {
                dis[v] = dis[u] + edge[i].cost;
                pre[v] = i;
                if(!vis[v])
                {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    if(pre[t] == -1)
        return 0;
    else
        return 1;
}
int minCostMaxflow(int s,int t,int &cost)
{
    int flow = 0;
    cost = 0;
    while(spfa(s,t))
    {
        int Min = INF;
        for(int i = pre[t]; i != -1; i = pre[edge[i^1].to])
            if(Min > edge[i].cap - edge[i].flow)
                Min = edge[i].cap - edge[i].flow;
        for(int i = pre[t]; i != -1; i = pre[edge[i^1].to])
        {
            edge[i].flow += Min;
            edge[i^1].flow -= Min;
            cost += edge[i].cost * Min;
        }
        flow += Min;
    }
    return flow;
}
char s[110][110];
struct node
{
    int x,y;
}man[10010],house[10010];
int main()
{
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int t,C = 1;
    //scanf("%d",&t);
    while(scanf("%d%d",&n,&m) && (n+m))
    {
        init();
        for(int i = 0; i < n; i++)
            scanf("%s",s[i]);
        int k1 = 0,k2 = 0;
        for(int i = 0; i < n; i++)
            for(int j = 0; j < m; j++)
            {
                if(s[i][j] == 'm')
                {
                    man[k1].x = i;
                    man[k1++].y = j;
                }
                if(s[i][j] == 'H')
                {
                    house[k2].x = i;
                    house[k2++].y = j;
                }
            }
        for(int i = 0; i < k1; i++)
        {
            add_edge(k1+k2,i,1,0);
            for(int j = 0; j < k2; j++)
            {
                int x = man[i].x - house[j].x;
                int y = man[i].y - house[j].y;
                add_edge(i,j+k1,1,abs(x)+abs(y));
            }
        }
        for(int i = 0; i < k2; i++)
            add_edge(k1+i,k1+k2+1,1,0);
        N = k1 + k2 + 2;
        int cost = 0;
        minCostMaxflow(k1+k2,k1+k2+1,cost);
        printf("%d\n",cost);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值