zoukankan      html  css  js  c++  java
  • 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
    ",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;
    }
    
  • 相关阅读:
    真机分享文件到虚拟机的centos 7 分享文件位置记录
    Linux系统列出某个用户组里的所有用户命令
    笔记之Linux命令vi
    笔记之Linux系统文件管理命令
    我的C#
    消息框
    ddt数据驱动在ui自动化中的应用二【多测师】
    ddt数据驱动在ui自动化中的应用一【多测师】
    基于ddt+unittest+Excel做接口测试自动化测试【多测师】
    Python操作MD5加密【多测师】
  • 原文地址:https://www.cnblogs.com/genshy/p/13602781.html
Copyright © 2011-2022 走看看