Our Journey of Dalian Ends(2017 ACM-ICPC 亚洲区(乌鲁木齐赛区)网络赛 J)

Problem Description

Life is a journey, and the road we travel has twists and turns, which sometimes lead us to unexpected places and unexpected people.

Now our journey of Dalian ends. To be carefully considered are the following questions.

Next month in Xian, an essential lesson which we must be present had been scheduled.

But before the lesson, we need to attend a wedding in Shanghai.

We are not willing to pass through a city twice.

All available expressways between cities are known.

What we require is the shortest path, from Dalian to Xian, passing through Shanghai.

Here we go.

Input

There are several test cases.

The first line of input contains an integer tt which is the total number of test cases.

For each test case, the first line contains an integer m~(m\le 10000)m (m≤10000) which is the number of known expressways.

Each of the following mm lines describes an expressway which contains two string indicating the names of two cities and an integer indicating the length of the expressway.

The expressway connects two given cities and it is bidirectional.

Output

For eact test case, output the shortest path from Dalian to Xian, passing through Shanghai, or output -1−1 if it does not exist.

Sample Input

3
2
Dalian Shanghai 3
Shanghai Xian 4
5
Dalian Shanghai 7
Shanghai Nanjing 1
Dalian Nanjing 3
Nanjing Xian 5
Shanghai Xian 8
3
Dalian Nanjing 6
Shanghai Nanjing 7
Nanjing Xian 8

​​​​​​​Sample ​​​​​​​Output

7
12
-1

题意:t 组数据,每组给出 m 个条路,问从Dalian 到 Xian 的最短路是多少,要求必须途径 shanghai 且每个城市只能经过一次,如果不满足,输出 -1

思路:最小费用最大流

当询问是否有多条没有相同点的带权从起点到终点时,一般可采用最大流来解决,其核心是用拆点来使得每个点只能走一次,由于本题路径带权,那么问题本质就是求一个最小费用最大流

本题给出的路径的点是字符串形式给出的,那么需要使用 map 来映射建图,从 1 号点开始,利用 map 来逐个映射点的编号,由于边为 m 条,那么点最多不会超过 n=2m 个

那么每个点就可拆为 x~x+n,这样拆点后的路的容量为 1,边权为 0,此外,由于上海需要走两次,那么其容量要设为 2,其他的边,按照原图中的连接即可,令拆点后的虚点在前,表示流出,实点在后,表示流入,容量为 1,边权为给出的 cost

之后,建立一个超级源点 S=0,连接大连和西安,容量为 1,费用为 0,再建立一个超级汇点,连接上海,容量为 2,费用为 0,然后跑 MCMF 即可,当从源点到汇点的流量为 2 时,说明从大连到上海、从西安到上海分别存在一条路径,此时输出最小边权和即可,反之,说明没有路径,输出 -1

Source Program

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define EPS 1e-9
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<int,int>
const int MOD = 1E9+7;
const int N = 200000+5;
const int dx[] = {1,-1,0,0,-1,-1,1,1};
const int dy[] = {0,0,-1,1,-1,1,-1,1};
using namespace std;
struct Edge{
    int from,to;
    int cap,flow;
    int cost;//费用
    int next;//指向下一条边的编号
}edge[N];
int head[N],tot;
int dis[N];//单源最短路径
bool vis[N];//SPFA中用于判断是否在队列
int pre[N];//记录从S到i的最小费用路径上的最后一条弧编号
void addEdge(int x,int y,int cap,int cost){
    edge[tot].from=x;
    edge[tot].to=y;
    edge[tot].cap=cap;
    edge[tot].flow=0;
    edge[tot].cost=cost;
    edge[tot].next=head[x];
    head[x]=tot++;

    edge[tot].from=y;
    edge[tot].to=x;
    edge[tot].cap=0;
    edge[tot].flow=0;
    edge[tot].cost=-cost;
    edge[tot].next=head[y];
    head[y]=tot++;
}
bool SPFA(int S,int T) {
    queue<int> Q;
    memset(dis,INF,sizeof(dis));
    memset(vis,false,sizeof(vis));
    memset(pre,-1,sizeof(pre));

    dis[S]=0;
    Q.push(S);

    while(!Q.empty()) {
        int x=Q.front();
        Q.pop();
        vis[x]=false;
        for(int i=head[x];i!=-1;i=edge[i].next) {
            int y=edge[i].to;
            int cost=edge[i].cost;
            int cap=edge[i].cap;
            int flow=edge[i].flow;
            if(dis[y]>dis[x]+cost&&cap>flow) {
                dis[y]=dis[x]+cost;
                pre[y]=i;
                if(!vis[y]){
                    vis[y]=true;
                    Q.push(y);
                }
            }
        }
    }
    return pre[T]!=-1;
}
void MCMF(int S,int T,int &flow,int &cost){
    while(SPFA(S,T)){//每次寻找花销最小的路径
        int minn=INF;
        for(int i=pre[T];i!=-1;i=pre[edge[i^1].to])//寻找最小增广流
            minn=min(minn,edge[i].cap-edge[i].flow);
        for(int i=pre[T];i!=-1;i=pre[edge[i^1].to]) {
            edge[i].flow+=minn;
            edge[i^1].flow-=minn;
            cost+=edge[i].cost*minn;//增广流的花销
        }
        flow+=minn;//总流量增加
    }
}
map<string,int> mp;
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(head,-1,sizeof(head));
        mp.clear();
        tot=0;

        int n,m;
        scanf("%d",&m);
        n=2*m;//最多2*m个点

        int cnt=0;//点编号
        for(int i=1;i<=m;i++){
            string x,y;
            int dis;
            cin>>x>>y>>dis;

            if(mp[x]==0){//每个点拆成两个点
                mp[x]=++cnt;
                if(x=="Shanghai")//上海要经过两次,容量设为2
                    addEdge(mp[x],mp[x]+n,2,0);
                else
                    addEdge(mp[x],mp[x]+n,1,0);
            }
            if(mp[y]==0){//每个点拆成两个点
                mp[y]=++cnt;
                if(y=="Shanghai")//上海要经过两次,容量设为2
                    addEdge(mp[y],mp[y]+n,2,0);
                else
                    addEdge(mp[y],mp[y]+n,1,0);
            }

            //建立双向边,虚点在前表示流出,实点在后表示流入
            addEdge(mp[x]+n,mp[y],1,dis);
            addEdge(mp[y]+n,mp[x],1,dis);
        }

        int S=0,T=4*n+1;
        addEdge(S,mp["Dalian"],1,0);
        addEdge(S,mp["Xian"],1,0);
        addEdge(mp["Shanghai"]+n,T,2,0);

        int flow=0,cost=0;
        MCMF(S,T,flow,cost);
        if(flow==2)
            printf("%d\n",cost);
        else
            printf("-1\n");
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值