HDU6181 Two Paths(次短路,路径记录,spfa,2017 HDU多校联赛 第10场)

Description

You are given a undirected graph with n nodes (numbered from 1 to n)
and m edges. Alice and Bob are now trying to play a game. Both of
them will take different route from 1 to n (not necessary simple).
Alice always moves first and she is so clever that take one of the
shortest path from 1 to n. Now is the Bob’s turn. Help Bob to take
possible shortest route from 1 to n. There’s neither multiple edges
nor self-loops. Two paths S and T are considered different if and only
if there is an integer i, so that the i-th edge of S is not the same
as the i-th edge of T or one of them doesn’t exist.

Input

The first line of input contains an integer T(1 <= T <= 15), the
number of test cases. The first line of each test case contains 2
integers n, m (2 <= n, m <= 100000), number of nodes and number of
edges. Each of the next m lines contains 3 integers a, b, w (1 <= a, b
<= n, 1 <= w <= 1000000000), this means that there’s an edge between
node a and node b and its length is w. It is guaranteed that there is
at least one path from 1 to n. Sum of n over all test cases is less
than 250000 and sum of m over all test cases is less than 350000.

Output

For each test case print length of valid shortest path in one line.

Sample Input

2
3 3
1 2 1
2 3 4
1 3 3
2 1
1 2 1

Sample Output

5
3

Hint

For testcase 1, Alice take path 1 - 3 and its length is 3, and then
Bob will take path 1 - 2 - 3 and its length is 5. For testcase 2, Bob
will take route 1 - 2 - 1 - 2 and its length is 3

思路

题意:

给出一个无向图,有n个点m条边和对应的权值,每两个点之间最多只有一条路。有两个人要从1号点走到n号点,第一个人走的是最短路径,第二个人也要走尽量短的路(次短路),但是要注意第二个人走的路径不能和第一个人相同,所以在第二组样例中,他先从1走到2,然后返回1,然后再走到2。

分析:

首先我们要求出次短路,我们求次短路的方法是:以点1为起点进行一遍spfa,再以点n为起点,进行一遍spfa,然后枚举除了1和n之外的所有点,找出从1~x的最短距离+从n~x的最短距离最小的那个值,这个值就是次短路。枚举的时候要注意:如果当前枚举的点在最短路径上出现过,那么就不枚举当前点,跳过。
当我们求出次短路以后,我们要判断当前的次短路是不是等于INF:
1. 如果等于的话,那么现在我们要在最短路径上的点的出边最短路径上的边中找一条边走两次,设这一条边的长度为minnc,最短路径为dis[n],那么现在第二个人走的路径是dis[n]+2*minc
2. 如果不等于的话,我们要取次短路在最短路的基础上把一条边走两次的最小值

解释一下为什么要在最短路及最短路的每个点的出边上找minc
这里写图片描述
如上图,当我们很容易看出从1~6的最短路为30,那么我们要把一条路走两次的话,如果仅仅的在最短路上找边的话只能找到10把10走两次,这样得到的结果是50,很明显这样是错误的,我们要找的是2,如果把2走两次的话,那么得到的结果是34,很明显,要在最短路上和其中每个点的出边找要走两次的那条边

代码

#include <cstdio>
#include <cstring>
#include <cctype>
#include <string>
#include <set>
#include <iostream>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <algorithm>
#define mem(a,b) memset(a,b,sizeof(a))
#define mod 1000007
#define M 12357
#define ll long long
using namespace std;
const ll N=100000+100;
const ll inf=1e18;
ll n,m,a,b,c,t;
struct SPFA
{
    ll first[N*4],len,vis[N],dis[N],inq[N],pre[N];
    vector<ll>path;
    void init()
    {
        mem(first,-1);
        mem(vis,0);
        mem(dis,0);
        mem(inq,0);//记录当前点是不是最短路径上的点
        len=1;
        path.clear();
        pre[1]=-1;
    }
    struct node
    {
        ll u,v,w,next;
    } G[N*4];
    void add_edge(ll u,ll v,ll w)
    {
        G[len].v=v,G[len].w=w;
        G[len].next=first[u];
        first[u]=len++;
    }
    void spfa(ll st)
    {
        for(ll i=1; i<=n; i++)
        {
            dis[i]=inf;
            vis[i]=0;
        }
        dis[st]=0;
        vis[st]=1;
        queue<ll>q;
        q.push(st);
        while(!q.empty())
        {
            st=q.front();
            q.pop();
            vis[st]=0;
            for(ll i=first[st]; i!=-1; i=G[i].next)
            {
                ll v=G[i].v,w=G[i].w;
                if(dis[v]>dis[st]+w)
                {
                    pre[v]=st;
                    dis[v]=dis[st]+w;
                    if(!vis[v])
                    {
                        vis[v]=1;
                        q.push(v);
                    }
                }
            }
        }
    }
    void print_path(int x)
    {
        if(pre[x]!=-1)
        {
            print_path(pre[x]);
            inq[x]=1;
            path.push_back(x);
        }
    }
} s1,s2;

int main()
{
    ll t;
    scanf("%lld",&t);
    while(t--)
    {
        scanf("%lld%lld",&n,&m);
        s1.init();
        s2.init();
        for(ll i=1; i<=m; i++)
        {
            scanf("%lld%lld%lld",&a,&b,&c);
            s1.add_edge(a,b,c);
            s1.add_edge(b,a,c);
            s2.add_edge(a,b,c);
            s2.add_edge(b,a,c);
        }
        s1.spfa(1);
        s1.print_path(n);
        s2.spfa(n);
        ll minn=inf,k=s1.dis[n];
        for(ll i=2; i<=n-1; i++)
        {
            if(s1.dis[i]+s2.dis[i]<=minn)
            {
                if(s1.inq[i]&&s1.dis[i]+s2.dis[i]==k)//如果当前用来松弛的点是最短路中出现过的点
                    continue;
                minn=s1.dis[i]+s2.dis[i];
            }
        }
        s1.path.push_back(1);//把1号点加入路径
        ll minc=inf;
        for(ll i=0; i<s1.path.size(); ++i)
        {
            for(ll j=s1.first[s1.path[i]]; ~j; j=s1.G[j].next)//遍历最短路径上的点的出边
            {
                ll w=s1.G[j].w;
                minc=min(minc,w);//找出最小的边
            }
        }
        if(n==2)
            printf("%lld\n",3*s1.dis[n]);
        else
        {
            if(minn==inf)
                printf("%lld\n",s1.dis[n]+2*minc);
            else
                printf("%lld\n",min(minn,s1.dis[n]+2*minc));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值