POJ2135 Farm Tour 无向图最小费用流

题意

  • 给你一个无向图,问你从1号节点到n号节点,再从n号节点返回1号节点,且中间不走重复路的最短路径。

思路

  • 开始的简单思路肯定是,先求一个最短路,再删除其中的边,然后再跑最短路。
  • 问题是,这样贪心的话,会出现有两个条最短路,我们不知道选哪个好的情况~这样不行
  • 我们想,用最小费用流来求解。
  • 其实1->n和n->1对于无向图来说是没有区别的,那么我们是不是可以认为就是从1->n去两次,那么也就是流量为2
  • 对于边,只能走一次,所以,我们认为边的容量限制是1,费用为边的长度
  • 这样就是一个F=2的无向图最小费用流问题了
  • 我们有有向图最小费用流模板,现在问题就是从无向图转向有向图
  • 由于一条边不需要两个方向都有流量(因为就抵消了),所以我们只需要把一条无向边变为两条方向相反的有向边,且它俩的容量和花费与无向边的一致即可!

实现

#include <queue>
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
const int INF = 0x3f3f3f3f;

//节点需从0开始编号,从1开始编号的话,需更改fill函数和init 
struct MinCostFlow{
    static const int MAX_V = 2000;

    //fi保存最短路径,se保存顶点编号 
    typedef pair<int,int> P;

    //用于表示边的结构体(终点、容量、费用、反向边)
    struct edge{
        int to, cap, cost, rev;
    }; 
    //顶点数
    int V;
    //图的邻接表                          
    vector<edge> G[MAX_V];
    //顶点的势       
    int h[MAX_V];
    //最短距离              
    int dist[MAX_V];
    //最短路中的前驱节点和对应的边 
    int prevv[MAX_V], preve[MAX_V];

    void add_edge(int from, int to, int cap, int cost){
        G[from].push_back((edge){to, cap, cost, G[to].size()});
        G[to].push_back((edge) {from, 0, -cost, G[from].size() - 1 });
    }

    int min_cost_flow(int s, int t, int f){
        int res = 0;
        fill(h, h + V, 0); //初始化h

        while (f > 0){
            // Dijkstra更新h
            priority_queue<P, vector<P>, greater<P> > que;
            fill(dist, dist + V, INF); 
            dist[s] = 0;
            que.push(P(0, s));

            while (!que.empty()){
                P p = que.top();
                que.pop();
                int v = p.second;
                if (dist[v] < p.first)
                    continue; 

                for (int i = 0; i < G[v].size(); i++){
                    edge& e = G[v][i];
                    if (e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]){
                        dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                        prevv[e.to] = v;
                        preve[e.to] = i;
                        que.push(P(dist[e.to], e.to));
                    }
                }
            }

            //不能再增广了 
            if (dist[t] == INF){
                //更改
                return -f; 
                //return -1;
            }

            for (int v = 0; v < V; v++){
                h[v] +=  dist[v];
            }

            //从s到t的最短路尽量增广 
            int d = f;
            for (int v = t; v != s; v = prevv[v]){
                d = min(d, G[prevv[v]][preve[v]].cap);
            }

            f -= d;
            res += d * h[t];
            for (int v = t; v != s; v = prevv[v]){
                edge &e = G[prevv[v]][preve[v]];
                e.cap -= d;
                G[v][e.rev].cap += d;
            }

        }
        return res; 
    }

    void init(){
        //这里假设节点从0编号 
        for (int i = 0; i < V; i++){
            G[i].clear();
        }
        V = 0;
    }

}mcf;


int n,m;

int main(){
    scanf("%d%d",&n,&m);
    mcf.V = n;
    for (int i = 0;i < m;i++){
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c); 
        mcf.add_edge(u-1, v-1, 1, c);
        mcf.add_edge(v-1, u-1, 1, c);
    }
    printf("%d\n",mcf.min_cost_flow(0, n-1, 2));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值