[CSP-J 2023] 旅游巴士

发布时间 2023-12-21 20:18:46作者: xinyimama

题目描述

小 Z 打算在国庆假期期间搭乘旅游巴士去一处他向往已久的景点旅游。

旅游景点的地图共有 \(n\) 处地点,在这些地点之间连有 \(m\) 条道路。其中 \(1\) 号地点为景区入口,\(n\) 号地点为景区出口。我们把一天当中景区开门营业的时间记为 \(0\) 时刻,则从 \(0\) 时刻起,每间隔 \(k\) 单位时间便有一辆旅游巴士到达景区入口,同时有一辆旅游巴士从景区出口驶离景区。

所有道路均只能单向通行。对于每条道路,游客步行通过的用时均为恰好 \(1\) 单位时间。

小 Z 希望乘坐旅游巴士到达景区入口,并沿着自己选择的任意路径走到景区出口,再乘坐旅游巴士离开,这意味着他到达和离开景区的时间都必须是 \(k\) 的非负整数倍。由于节假日客流众多,小 Z 在旅游巴士离开景区前只想一直沿着景区道路移动,而不想在任何地点(包括景区入口和出口)或者道路上停留

出发前,小 Z 忽然得知:景区采取了限制客流的方法,对于每条道路均设置了一个
“开放时间”\(a _ i\),游客只有不早于 \(a _ i\) 时刻才能通过这条道路。

请帮助小 Z 设计一个旅游方案,使得他乘坐旅游巴士离开景区的时间尽量地早。

输入格式

输入的第一行包含 3 个正整数 \(n, m, k\),表示旅游景点的地点数、道路数,以及旅游巴士的发车间隔。

输入的接下来 \(m\) 行,每行包含 3 个非负整数 \(u _ i, v _ i, a_ i\),表示第 \(i\) 条道路从地点 \(u _ i\) 出发,到达地点 \(v _ i\),道路的“开放时间”为 \(a _ i\)

输出格式

输出一行,仅包含一个整数,表示小 Z 最早乘坐旅游巴士离开景区的时刻。如果不存在符合要求的旅游方案,输出 -1

样例 #1

样例输入 #1

5 5 3
1 2 0
2 5 1
1 3 0
3 4 3
4 5 1

样例输出 #1

6

提示

【样例 #1 解释】

小 Z 可以在 \(3\) 时刻到达景区入口,沿 \(1 \to 3 \to 4 \to 5\) 的顺序走到景区出口,并在 \(6\) 时刻离开。

【样例 #2】

见附件中的 bus/bus2.inbus/bus2.ans

【数据范围】

对于所有测试数据有:\(2 \leq n \leq 10 ^ 4\)\(1 \leq m \leq 2 \times 10 ^ 4\)\(1 \leq k \leq 100\)\(1 \leq u _ i, v _ i \leq n\)\(0 \leq a _ i \leq 10 ^ 6\)

测试点编号 \(n \leq\) \(m \leq\) \(k \leq\) 特殊性质
\(1 \sim 2\) \(10\) \(15\) \(100\) \(a _ i = 0\)
\(3 \sim 5\) \(10\) \(15\) \(100\)
\(6 \sim 7\) \(10 ^ 4\) \(2 \times 10 ^ 4\) \(1\) \(a _ i = 0\)
\(8 \sim 10\) \(10 ^ 4\) \(2 \times 10 ^ 4\) \(1\)
\(11 \sim 13\) \(10 ^ 4\) \(2 \times 10 ^ 4\) \(100\) \(a _ i = 0\)
\(14 \sim 15\) \(10 ^ 4\) \(2 \times 10 ^ 4\) \(100\) \(u _ i \leq v _ i\)
\(16 \sim 20\) \(10 ^ 4\) \(2 \times 10 ^ 4\) \(100\)

题解

对于这个题而言,我们要在图上走一下,走出k的整数倍的路径长度,求这样的问题应该如何解?比如下图,

如果K为5的话,显然我们转一圈会更好,所以说,对于每个点来说,我们并不是要求最短路路径,当然,遇到这个k的整数倍的问题,我们知道一定是和模有关,但是这个模要怎么用?我一只思考不明白这个问题,
正确的想法是,同时也是一般的设计想法是,我们把模设计到状态中,dis[u][x]表示到达u的在模k意义下为x的最短路径,我们正常去跑最短路即可,最终目标状态是dis[n][0],我们用迪杰斯特拉的思路来写就行。问题来了,这样的写法,时间复杂度是多少?
我们可以来类比迪杰斯特拉的时间复杂度分析
对于每一条边,他的终端v,会有k次进队的机会,所以进队的操作是mklognk,删除堆顶的操作为nklognk,随意最终的时间复杂度为(mk+nk)lognk

但是第一次我写错了,一直T,哪里写错了呢?

点击查看代码
        int u=q.top().u;
        int y=q.top().ys;
        int dd=q.top().dis;
        if(u==n&&y==0){
            ans=dd;
            return;
        }
        
        if(b[u][y]) continue;
        b[u][y]=1;
        
        q.pop();//这里写错了,如果这么写的话,如果访问过了,就continue了,就不会pop出去了,这里导致我一直T,记住

最终AC代码:

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=10005;
const int maxm=20005;
struct Edge{
    int to,nxt,ai;
}ed[maxm];
int n,m,k,head[maxn],ecnt,ans=2147483647;
void addedge(int u,int v,int ai){
    ed[++ecnt].to=v;
    ed[ecnt].ai=ai;
    ed[ecnt].nxt=head[u];
    head[u]=ecnt;
}
struct node{
    int dis,u,ys;
    bool operator<(node s)const{
        return dis>s.dis;
    }
    node(int a,int b,int c){
        dis=a;
        u=b;
        ys=c;
    }
};
priority_queue< node >q;
int dis[maxn][105];
bool b[maxn][105];
void bfs(){
    q.push(node(0,1,0));
    memset(dis,0x3f,sizeof dis);
    dis[1][0]=0;
    while(!q.empty()){
        int u=q.top().u;
        int y=q.top().ys;
        int dd=q.top().dis;
        if(u==n&&y==0){
            ans=dd;
            return;
        }
        q.pop();
        if(b[u][y]) continue;
        b[u][y]=1;
        for(int i=head[u];i;i=ed[i].nxt){
            int v=ed[i].to;
            int bs;
            if(ed[i].ai>dd){
                bs=ceil((ed[i].ai-dd)*1.0/k);//早出发多少倍
            }else bs=0;
            int yy=(dd+1)%k;
            if(dis[v][yy]>dd+1+bs*k){
                dis[v][yy]=dd+1+bs*k;
                q.push(node(dd+1+bs*k,v,yy));
            }
        }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        addedge(a,b,c);
    }
    bfs();
    if(ans==2147483647) printf("-1");
    else printf("%d",ans);
    return 0;
}
/**
9 14 95
7 9 0
6 9 0
4 2 0
3 4 0
5 7 0
7 9 0
8 7 0
5 3 0
8 7 0
1 6 0
5 3 0
2 8 0
3 1 0
6 5 0
 */