最大流最小费用 dij+势函数+前向星 POJ2195

#include <stdio.h>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <queue>
#include <math.h>
using namespace std;
#define INF 0x3f3f3f3f
#define fi first
#define se second
struct node
{
    int x , y;
}hourse[20000],man[20000];
struct e
{
    int to;
    int c;
    int w;
    int next;
}edge[200*200*200];
int head[500] , h[500], cnt, dis[1000], pre[1000], pc[1000], n ,m ,hourseCnt , manCnt, st, ed;
//h为势函数数组,pc为到达当前的流,pre记录路径,dis记录距离
int ansflow , anscost;
void add(int f, int t, int c, int w)
{
    edge[cnt].to = t;
    edge[cnt].c = c;
    edge[cnt].next = head[f];
    edge[cnt].w = w;
    head[f] = cnt ++;
}
void dij()
{
    priority_queue <pair<int,int> > q;
    while(true) //从汇点出发,不断寻找花费最短路作为增广路
    {
        memset(dis,INF,sizeof(dis));
        dis[st] = 0;
        pre[st] = -1;
        pc[st] = INF; //用于记录该增广路所能承受的最大流
        q.push(make_pair(0,st));
        while(!q.empty()) //采用堆优化的dij寻找
        {
            pair<int,int> p = q.top();
            q.pop();
            int u = p.se;
            if(-p.fi > dis[u]) continue;
            for(int i = head[u] ; i != -1 ; i = edge[i].next)
            {
                int v = edge[i].to , c = edge[i].c, w = edge[i].w;
                int ww = w + h[u] - h[v]; //引入势函数作为新权,保证正数,从而让成功,类似于Johnson算法
                if(c > 0 && dis[u] + ww < dis[v])
                {
                    dis[v] = dis[u] + ww; //修改距离
                    q.push(make_pair(-dis[v],v));
                    pre[v] = i; //记录路径
                    pc[v] = min(pc[u],c); //修改最大流
                }
            }
        }
        if(dis[ed] == INF) //如果为INF说明不存在增广路,跳出即可
            break;
        for(int i = st ; i <= ed ; i ++) h[i] += dis[i]; //更新势函数
        int nowflow = pc[ed];
        ansflow += nowflow;
        anscost += h[ed]*nowflow;
        for(int i = ed ; pre[i] != -1 ; i = edge[pre[i]^1].to) //递归回溯路径,改变流大小
        {
            edge[pre[i]].c -= nowflow;
            edge[pre[i]^1].c += nowflow;
        }
    }
}
int main(void)
{
    while(scanf("%d %d",&n , &m) != EOF)
    {
        memset(pre,0,sizeof(pre));
        memset(pc,0,sizeof(pre));
        memset(h,0,sizeof(h));
        if(n == 0 && m == 0)
            return 0;
        memset(head,-1,sizeof(head));
        cnt = hourseCnt = manCnt = ansflow = anscost = 0;
        char ch[105];
        for(int i = 0 ; i < n ; i ++) {
                scanf("%s",ch);
                for(int j = 0 ; j < m ; j ++){
                    if(ch[j] == 'm')
                         man[manCnt].x = i+1 , man[manCnt].y = j+1 , manCnt ++;
                     if(ch[j] == 'H')
                            hourse[hourseCnt].x = i+1 , hourse[hourseCnt].y = j+1 , hourseCnt ++;
            }
        }
        st = 0 , ed = 1+2*manCnt;
        for(int i = 1 ; i <= manCnt ; i ++)
        {
            add(st,i,1,0);
            add(i,st,0,0);
        }
        for(int i = 0 ; i < manCnt ; i ++)
        {
            for(int j = 0 ; j < hourseCnt ; j ++)
            {
                int w = abs(man[i].x-hourse[j].x) + abs(man[i].y - hourse[j].y);
                add(1+i,1+manCnt+j,1,w);
                add(1+manCnt+j,1+i,0,-w);
            }
        }
        for(int i = 0 ; i < hourseCnt ; i ++)
        {
            add(1+manCnt+i,ed,1,0);
            add(ed,1+manCnt+i,0,0);
        }
        dij();
        printf("%d\n",anscost);
    }
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值