[CSP-J 2023] 旅游巴士
题目描述
小 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.in
与 bus/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
*/