TOUR(最小费用流—有向环覆盖)(模板)

本文介绍了一个使用最小费用最大流算法解决特定旅行路线设计问题的方法。该问题是找到一条包含一个或多个循环的路线,使得每个城市恰好被访问一次,并且所选路线的总距离最短。

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

In the kingdom of Henryy, there are N (2 <= N <= 200) cities, with M (M <= 30000) one-way roads connecting them. You are lucky enough to have a chance to have a tour in the kingdom. The route should be designed as: The route should contain one or more loops. (A loop is a route like: A->B->……->P->A.) 
Every city should be just in one route. 
A loop should have at least two cities. In one route, each city should be visited just once. (The only exception is that the first and the last city should be the same and this city is visited twice.) 
The total distance the N roads you have chosen should be minimized. 
Input
An integer T in the first line indicates the number of the test cases. 
In each test case, the first line contains two integers N and M, indicating the number of the cities and the one-way roads. Then M lines followed, each line has three integers U, V and W (0 < W <= 10000), indicating that there is a road from U to V, with the distance of W. 
It is guaranteed that at least one valid arrangement of the tour is existed. 
A blank line is followed after each test case.
Output
For each test case, output a line with exactly one integer, which is the minimum total distance.
Sample Input
1
6 9
1 2 5
2 3 5
3 1 10
3 4 12
4 1 8
4 6 11
5 4 7
5 6 9
6 5 4
Sample Output

42


大意:

在Henryy王国,有N(2 <= N <= 200)个城市,其中M(M <= 30000)个单向道路连接它们。你很幸运有机会参观这个王国。路线应设计为:路线应包含一个或多个循环。 (循环是一条路线,如:A-> B-> ...... - > P-> A)
每个城市都应该在一条路上。
一个循环应该至少有两个城市。在一条路线上,每个城市应该只被访问一次。 (唯一的例外是第一个和最后一个城市应该访问两次。)

您选择的N条道路的总距离应尽可能小。

思路分析:

1.由于每个点只能走一次,所以就想到必须要拆点。flow=1,fee=0

2.由于是覆盖。源点到每个点i建边,i+n到汇点建边

3.如果i—>j有边,则建边i->j+n,花费fee

4.如果最大流==n,则最小花费即为解。

也不难理解,

我们选的这些边是i->j+n(左边点集到右边)。

这n条边的起点覆盖了n各不同的顶点。终点也是。

一条边的终点是另一条边的起点。(这个点即是起点也是终点。圈)

最终我们通过最小费用最大流选的这n条边必然组成了1个(或多个)不相交的有向环且费用最小(必要条件,新问题有解->原问题有解)(如果还存在费用更小的有向环,那么我们的算法肯定会找到(充分条件,即原问题有解->新问题有解).

这种证明方式自己慢慢斟酌。我是参考http://blog.youkuaiyun.com/u013480600/article/details/39159407

建好了就可以了。这种题还可以用二分匹配KM写,暂时还未写。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<iostream>
#include<vector>
#define INF 1e9
using namespace std;
const int maxn=400+5;

struct Edge
{
    int from,to,cap,flow,cost;
    Edge(){}
    Edge(int f,int t,int c,int fl,int co):from(f),to(t),cap(c),flow(fl),cost(co){}
};

struct MCMF
{
    int n,m,s,t;
    vector<Edge> edges;
    vector<int> G[maxn];
    bool inq[maxn];
    int d[maxn];
    int p[maxn];
    int a[maxn];

    void init(int n,int s,int t)
    {
        this->n=n, this->s=s, this->t=t;
        edges.clear();
        for(int i=0;i<n;++i) G[i].clear();
    }

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

    bool BellmanFord(int &flow,int &cost)
    {
        queue<int> Q;
        for(int i=0;i<n;++i) d[i]=INF;
        memset(inq,0,sizeof(inq));
        Q.push(s),inq[s]=true,d[s]=0,a[s]=INF,p[s]=0;

        while(!Q.empty())
        {
            int u=Q.front(); Q.pop();
            inq[u]=false;
            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;
                    a[e.to]=min(a[u],e.cap-e.flow);
                    p[e.to]=G[u][i];
                    if(!inq[e.to]){inq[e.to]=true; Q.push(e.to);}
                }
            }
        }
        if(d[t]==INF) return false;
        flow += a[t];
        cost += a[t]*d[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;
    }

    int solve(int num)
    {
        int flow=0,cost=0;
        while(BellmanFord(flow,cost));
        return flow==num?cost:-1;
    }
}MM;

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        int src=0,dst=2*n+1;
        MM.init(2*n+2,src,dst);
        for(int i=1;i<=n;i++)
        {
            MM.AddEdge(src,i,1,0);
            MM.AddEdge(i+n,dst,1,0);
        }
        while(m--)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            MM.AddEdge(u,v+n,1,w);
        }
        printf("%d\n",MM.solve(n));
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值