P2832 行路难

题面

Link

题目背景

小X来到了山区,领略山林之乐。在他乐以忘忧之时,他突然发现,开学迫在眉睫

题目描述

山区有 \(n\) 座山。山之间有 \(m\) 条羊肠小道,每条连接两座山,只能单向通过,并会耗费小X一定时间。

小X现在在 \(1\) 号山,他的目的是 \(n\) 号山,因为那里有火车站。

然而小X的体力是有限的。他每通过一条羊肠小道,就会变得更疲劳,导致他通过任意一条羊肠小道的时间都增加 \(1\)

输入格式

第一行两个数,\(n\),\(m\)

\(2\) 行到第 \(m+1\) 行,每行 \(3\) 个数 \(A\)\(B\)\(C\),表示 \(A\)\(B\) 之间有一条羊肠小道,可以让小X花费 \(C\) 的时间从 \(A\) 移动到 \(B\)

输出格式

两行 第一行一个数 \(T\),表示小X需要的最短时间

第二行若干个数,用空格隔开,表示小X的移动路线
例:1 4 2 5表示:小X从1号山开始,移动到4号山,再到2号山,最后到5号山。

输入输出样例

输入 #1

5 8
2 4 2
5 2 1
1 2 1
4 3 2
1 3 3
4 5 2
1 5 8
3 5 3

输出 #1

7
1 3 5

说明/提示

n<=10000, m<=200000

数据保证没有多条最短路径

题解

本来以为这道题是道水题,能一遍过的。

结果没想到,这道题太过毒瘤,导致我交了好几遍(

我们的正常思路就是 记录到每个点经过的的边的数量

转移距离时算上 f[t] 来进行更新 。

在从一跑一遍最短路,统计答案就记一下每个点的前驱,就完事了。

核心代码长这样


void spfa()
{
    queue<int> q;
    memset(dis,127,sizeof(dis));//dis初值设为极大值
    q.push(1); dis[1] = 0; vis[1] = 1;
    while(!q.empty())
    {
        int t = q.front(); q.pop(); vis[t] = 0;
        for(int i = head[t]; i; i = e[i].net)
        {
            int to = e[i].to;
            int tmp = f[t] + 1;
            if(dis[to] > dis[t] + e[i].w + f[t])//算上累计的疲劳度
            {
                dis[to] = dis[t] + e[i].w + f[t];
                f[to] = f[t] + 1;//更新一下 f[to]
                pre[to] = t;//记录前驱
                if(!vis[to])
                {
                    q.push(to);
                    vis[to] = 1;
                }
            }
        }
    }
}

当你满怀信心交上去时,却发现最后两个点 WA 了。

我想了想,看了看别人的帖子,发现有一组 hack 数据

hack1:
INPUT:
7 7
1 2 2
2 3 2
3 4 2
4 5 2
5 6 2
6 7 2
1 4 10
OUTPUT:
22
1 4 5 6 7

hack2:
INPUT:
7 7
1 2 1
2 3 1
3 4 1
4 5 1
5 6 1
6 7 1
1 5 14
OUTPUT:
19
1 5 6 7

我们把这张图画下来,他张这样

我们问题主要出现在 1-2-3-4-5-6-7 和 1-4-5-6-7 这两条路径,他们的路径长度相同。

但正解却是路径二,因为路径二的疲劳值比 一小。

按照我们正常的做法就会被 HACK 掉。

那我们怎么解决这个问题呢?

这就要用到一个玄学的做法,建反图倒着跑最短路。

证明一下:

假设我们有两条路径 1-2-3-4-5-6-7 和 1-4-5-6-7

当我们更新到 4 的时候,有两条路 到三或者直接到 一。

我们反着跑最短路就会先更新 一,这就避免了上面的那种情况。

这就相当于我们能到终点就直接到终点,而不是走弯路。

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e5+10;
int n,m,tot,u,v,w,cnt;
int head[N],dis[N],f[N],pre[N],a[N];
bool vis[N];
struct node
{
    int to,net,w;
}e[200010];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s =s * 10+ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y,int w)
{
    e[++tot].w = w;
    e[tot].to = y;
    e[tot].net = head[x];
    head[x] = tot;
}
void spfa()
{
    queue<int> q;
    memset(dis,127,sizeof(dis));
    q.push(n); dis[n] = 0; vis[n] = 1;
    while(!q.empty())
    {
        int t = q.front(); q.pop(); vis[t] = 0;
        for(int i = head[t]; i; i = e[i].net)
        {
            int to = e[i].to;
            int tmp = f[t] + 1;
            if(dis[to] > dis[t] + e[i].w + f[t])
            {
                dis[to] = dis[t] + e[i].w + f[t];
                f[to] = f[t] + 1;
                pre[to] = t;
                if(!vis[to])
                {
                    q.push(to);
                    vis[to] = 1;
                }
            }
        }
    }
}
int main()
{
    n = read(); m = read();
    for(int i = 1; i <= m; i++)
    {
        u = read(); v = read(); w = read();
        add(v,u,w);///建反图跑最短路
    }
    spfa();    
    printf("%d\n",dis[1]);
    int x = 1;
    while(x != n)
    {
    	a[++cnt] = x;
    	x = pre[x];
    }
    a[++cnt] = n;
    for(int i = 1; i <= cnt; i++)//输出路径
    {
    	printf("%d ",a[i]);
    }
    return 0;
}
posted @ 2020-09-02 21:17  genshy  阅读(173)  评论(0编辑  收藏  举报