D - Going Home POJ - 2195 网路流 - 最小费用最大流

本文深入探讨了最小费用最大流算法,通过实例讲解如何解决特定类型的问题,如分配人员到房屋以达到最小成本。文章提供了详细的算法实现,包括初始化、边的添加、SPFA算法的使用以及最终的MCMF函数,适用于理解和实现最小费用最大流问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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));
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值