HITOJ 2739 The Chinese Postman Problem(有向带权图上的中国邮递员问题)

87 篇文章 0 订阅

The Chinese Postman Problem
Source : bin3
Time limit : 1 sec Memory limit : 64 M
Submitted : 530, Accepted : 177
A Chinese postman is assigned to a small town in China to deliver letters. In this town, each street is oriented and connects exactly two junctions. The postman’s task is to start at the post office and pass each street at least once to deliver letters. At last, he must return to the post office.
Can you help him to make sure whether there exist feasible routes for him and find the minimum distance from all the feasible routes.
Input
Input contains multiple test cases. The first line is an integer T, the number of test cases. Each case begins with two integers N, M, with 2 ≤ N ≤ 100, 1 ≤ M ≤ 2000, representing the number of junctions and the number of streets respectively.
Then M lines will follow, each denoting a street. A street is represented by three integers u, v, d, with 0 ≤ u, v < N, 0 < d ≤ 1000, meaning this street whose length is d connects the junction u and v and the postman can only travel from junction u to v. Junctions are numbered from 0 to N-1. Junction 0 is always the post office. Note that there may be more than one street connecting the same pair of junctions.
Output
Output one line for each test case. If there exist feasible routes for the postman, output the minimum distance. Otherwise, output -1.
Sample Input
3
2 1
0 1 3
4 4
0 1 1
1 2 2
2 3 3
3 0 4
4 7
0 1 1
1 2 2
2 3 3
3 0 4
1 3 5
3 1 2
1 3 2
Sample Output
-1
10
21

题目大意:

  给你一个有向带边权图,求从任意点出发进过所有边之后回到这个点的最小费用(边可以走多次)。

解题思路:

  首先可以知道,如果存在入度或出度为0的点则无解。并且,如果不连通也无解。
  假设我们先经过每条边一遍,则入度不等于出度的点的流量就出现了不平衡,为了使流量平衡,我们不断添加从出度大于入度到入度大于出度的路径直到平衡为止。
  可以通过最小费用最大流实现这个过程,并使得花费最小。令 D(i) i 号结点的入度减出度,如果D(i)>0则连一条 i 到汇点容量为D(i)费用为0的边,如果 D(i)<0 则连一条源点到 i 容量为D(i)费用为0的边。对于原图中的每条边起点终点不变,容量为 <script type="math/tex" id="MathJax-Element-832">∞</script>费用为原费用。跑一遍最小费用最大流再加上原图所有边的权就是答案。

AC代码

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <cstdlib>
#include <cmath>
#include <string>
#include <map>
using namespace std;
#define INF 0x3f3f3f3f
#define LL long long
#define fi first
#define se second
#define mem(a,b) memset((a),(b),sizeof(a))

const int MAXV=100+3;
const int MAXE=2000+3;

struct Edge
{
    int to,next,cap,cost;
    Edge(int t=0,int n=0,int ca=0,int f=0,int co=0):to(t),next(n),cap(ca),cost(co){}
}edge[2*(MAXE+MAXV)];

int V, E;
int in[MAXV], out[MAXV];
int head[MAXV],tol;
int pre[MAXV],dis[MAXV];
bool vis[MAXV];

void add_edge(int u,int v,int cap,int cost)
{
    edge[tol]=Edge(v,head[u],cap,0,cost);
    head[u]=tol++;
    edge[tol]=Edge(u,head[v],0,0,-cost);
    head[v]=tol++;
}

bool spfa(int s,int t)
{
    queue<int> que;
    for(int i=0;i<V;++i)
    {
        dis[i]=INF;
        vis[i]=false;
        pre[i]=-1;
    }
    dis[s]=0;
    vis[s]=true;
    que.push(s);
    while(!que.empty())
    {
        int u=que.front(); que.pop();
        vis[u]=false;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(edge[i].cap>0&&dis[v]>dis[u]+edge[i].cost)
            {
                dis[v]=dis[u]+edge[i].cost;
                pre[v]=i;
                if(!vis[v])
                {
                    vis[v]=true;
                    que.push(v);
                }
            }
        }
    }
    return pre[t]!=-1;
}

int min_cost_flow(int s,int t,int &cost)//返回的是最大流,cost存的是最小费用
{
    int flow=0;
    cost=0;
    while(spfa(s,t))
    {
        //如果流量任意,当spfa跑出的dis[t]>=0时跳出循环即可
        int the_min=INF;
        for(int i=pre[t];i!=-1;i=pre[edge[i^1].to])
            the_min=min(the_min, edge[i].cap);
        for(int i=pre[t];i!=-1;i=pre[edge[i^1].to])
        {
            edge[i].cap-=the_min;
            edge[i^1].cap+=the_min;
            cost+=edge[i].cost*the_min;
        }
        flow+=the_min;
    }
    return flow;
}

int par[MAXV], high[MAXV];

int findfather(int x)
{
    return par[x]=par[x]==x?x:findfather(par[x]);
}

bool unite(int a, int b)
{
    int fa=findfather(a), fb=findfather(b);
    if(fa==fb)
        return false;
    if(high[fa]>high[fb])
        par[fb]=fa;
    else
    {
        par[fa]=fb;
        if(high[fa]==high[fb])
            ++high[fb];
    }
    return true;
}

void init()
{
    for(int i=0;i<V+2;++i)
    {
        head[i]=-1;
        par[i]=i;
        high[i]=0;
        in[i]=out[i]=0;
    }
    tol=0;
}

int main()
{
    int T_T;
    scanf("%d", &T_T);
    while(T_T--)
    {
        scanf("%d%d", &V, &E);
        init();
        int sum=0;
        for(int i=0;i<E;++i)
        {
            int u, v, c;
            scanf("%d%d%d", &u, &v, &c);
            unite(u, v);
            ++out[u];
            ++in[v];
            add_edge(u, v, INF, c);
            sum+=c;
        }
        bool ok=true;
        for(int i=1;i<V;++i)
            if(findfather(0)!=findfather(i))//如果不连通,则无解
            {
                ok=false;
                break;
            }
        int s=V, t=V+1;
        for(int i=0;i<V;++i)
        {
            if(in[i]==0 || out[i]==0)//存在入度或出度为0的点,无解
            {
                ok=false;
                break;
            }
            if(out[i]-in[i]>0)
                add_edge(i, t, out[i]-in[i], 0);
            else if(out[i]-in[i]<0)
                add_edge(s, i, in[i]-out[i], 0);
        }
        if(!ok)
        {
            puts("-1");
            continue;
        }
        V+=2;
        int ans;
        min_cost_flow(s, t, ans);
        printf("%d\n", ans+sum);
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值