计蒜客-Our Journey of Dalian Ends 拆点+最小费用最大流

本文介绍了一种利用最小费用最大流算法解决特定旅行路线问题的方法。通过拆点和添加特殊边的方式,构造了一个适合应用该算法的图模型,最终求解出从上海出发到达大连的最短路径。

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

题目

Our Journey of Dalian Ends

题解

需要用到最小费用最大流的知识,不会的话可以先学习一下。
有了这个前置知识,这道题就很简单了。

首先由于每个城市只能经过一次,所以要先进行拆点。将一个城市拆成两个点,这两个点单向流动,容量为1,路径长度为0。

点拆好后,要再加两个点,三条路径。一个超级源点,连接上海,流量为2,路径长度为1;一个超级汇点,分别连大连和上海,流量为1,路径长度为0。

最后跑一个从超级源点到超级汇点的最小费用最大流就好了。如果答案流量为2,就把费用输入,但如果流量小于2,就输出-1。

代码

#include <algorithm>
#include <bitset>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <climits>
#include <iostream>
#include <list>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
using namespace std;

const int MAX = 50000;
struct Edge
{
    int from,to;
    long long cap,flow,cost;
    Edge(int u,int v,long long ca,long long f,long long co):from(u),to(v),cap(ca),flow(f),cost(co){};
};

struct MCMF
{
    int n;
    vector<Edge> edges;
    vector<int> G[MAX];
    int inq[MAX];//是否在队列中
    long long d[MAX];//距离
    int p[MAX];//上一条弧
    long long a[MAX];//可改进量

    void init(int n)//初始化
    {
        this->n=n;
        for(long long i=0;i<=n;i++)
            G[i].clear();
        edges.clear();
    }

    void addedge(int from,int to,long long cap,long long cost)//加边
    {
        edges.push_back(Edge(from,to,cap,0,cost));
        edges.push_back(Edge(to,from,0,0,-cost));
        int m = edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }

    bool SPFA(int s,int t,long long &flow,long long &cost)//寻找最小费用的增广路,使用引用同时修改原flow,cost
    {
        for(int i=1;i<=n;i++)
            d[i] = LLONG_MAX;
        memset(inq,0,sizeof(inq));
        d[s]=0;inq[s]=1;p[s]=0;a[s]=LLONG_MAX;
        queue<int> Q;
        Q.push(s);
        while(!Q.empty()) {
            int u=Q.front();
            Q.pop();
            inq[u]--;
            for(int i=0;i<G[u].size();i++)
            {
                Edge& e=edges[G[u][i]];
                if(e.cap>e.flow && d[e.to]>d[u]+e.cost)//满足可增广且可变短
                {
                    d[e.to]=d[u]+e.cost;
                    p[e.to]=G[u][i];
                    a[e.to]=min(a[u],e.cap-e.flow);
                    if(!inq[e.to])
                    {
                        inq[e.to]++;
                        Q.push(e.to);
                    }
                }
            }
        }
        if(d[t]==LLONG_MAX)
            return false;//汇点不可达则退出
        flow += a[t];
        cost += d[t] * a[t];
        int u = t;
        while(u != s)//更新正向边和反向边
        {
            edges[p[u]].flow += a[t];
            edges[p[u]^1].flow -= a[t];
            u = edges[p[u]].from;
        }
        return true;
    }

    long long MincotMaxflow(int s,int t)
    {
        long long flow=0,cost=0;
        while(SPFA(s,t,flow,cost));
        if(flow < 2)
            return -1;
        return cost;
    }
};

map<string, int>m;
int main(){
    int t;
    cin >> t;
    while(t--){
        MCMF mcmf;
        string a,b;
        long long len;
        int n;

        m.clear();
        cin >> n;

        int N = 2 * n + 10;
        int cnt = 6;

        mcmf.init(N * 2);

        m["Shanghai"] = 3;
        mcmf.addedge(3,3 + N,2,0);
        m["Xian"] = 4;
        mcmf.addedge(4,4 + N,1,0);
        m["Dalian"] = 5;
        mcmf.addedge(5,5 + N,1,0);

        for(int i=0;i<n;++i){
            cin >> a >> b;
            scanf("%lld",&len);
            if(m.find(a) == m.end()){
                m[a] = cnt++;
                mcmf.addedge(m[a],m[a] + N,1,0);
            }
            if(m.find(b) == m.end()){
                m[b] = cnt++;
                mcmf.addedge(m[b],m[b] + N,1,0);
            }
            mcmf.addedge(m[a] + N,m[b],1,len);
            mcmf.addedge(m[b] + N,m[a],1,len);
        }

        mcmf.addedge(1,m["Shanghai"],2,0);
        mcmf.addedge(m["Xian"] + N,2,1,0);
        mcmf.addedge(m["Dalian"] + N,2,1,0);

        cout << mcmf.MincotMaxflow(1,2) << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值