#include using namespace std; const int N = 10010, M = N << 1, K = 110; // 节点个数上限,边数上限,发车间隔时长上限 const int INF = 0x3f3f3f3f; struct Node { int u, r, d; // u节点编号,r状态,d最短到达时间 // 重载 < ,优先队列,默认使用大顶堆,我们需要小顶堆,所以,需要重载小于号,描述两个Node对比, // 距离大的反而小,由于是大顶堆,所以距离小的在上 bool operator<(const Node t) const { return d > t.d; } }; // 链式前向星 int e[M], h[N], idx, w[M], ne[M]; void add(int a, int b, int c = 0) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } /* dis[u][i]表示到达第u处地点,并且到达时间mod k = i的情况下的最短距离 */ int dis[N][K], st[N][K]; int main() { memset(h, -1, sizeof h); // 初始化链式前向星 int n, m, k; cin >> n >> m >> k; while (m--) { int a, b, c; cin >> a >> b >> c; add(a, b, c); // 从a到b建一条边,c为此路径的开放时间 } // 初始状态,1号点在状态0时最短距离为0,其它点的最短距离为无穷大 memset(dis, 0x3f, sizeof dis); // dis是一个二维结果数组,一维是节点号,二维是此节点在不同状态下可以到达的最短路径 dis[1][0] = 0; // 所谓状态,是指到达时间(对于1号节点而言就是出发时间)%k的余数值 // 因为题目中说到,1号点和n号点都必须是%k=0的时间,所以,dis[1][0]=0 // 描述1号节点,在状态0下出发,距离出发点的距离是0 // 堆优化版Dijkstra求最短路,注意默认大顶堆,自定义比较规则 priority_queue q; // 初始状态加入优先队列,{点,状态,最短到达时间} q.push({1, 0, dis[1][0]}); // 节点号,%k的值,已经走过的距离 // 每个进队列的节点,都有3个属性:节点号,%k的值,已经走过的距离 while (q.size()) { // 从1号点开始宽搜 int u = q.top().u; // 节点编号u int r = q.top().r; // 节点状态r q.pop(); if (st[u][r]) continue; // 该状态已经加入到集合中,也就是已经被搜索过 // 先被搜索过在队列宽搜中意味着已经取得了最短路径 st[u][r] = 1; for (int i = h[u]; ~i; i = ne[i]) { // 枚举邻接点v和连接到v节点道路的开放时间 int v = e[i]; // v节点,也就是下一个节点 int t = w[i]; // v节点的开放时间 int d = dis[u][r]; // 到达(u,r)这个状态时的最短距离d int j = (r + 1) % k; // v节点的状态应该是u节点的状态+1后再模k // 如果到达时间小于开放时间,则将到达时间向后延长若干个k的整数倍(向上取整) while (d < t) d += k; // 如果可以松弛到v点的时间 if (dis[v][j] > d + 1) { // 下一个节点v的j状态,可以通过(u,i)进行转移,那么可以用t+1更尝试更新掉dis[v][j] dis[v][j] = d + 1; q.push({v, j, dis[v][j]}); // 再用(v,j)入队列去更新其它节点数据,标准的Dijkstra算法 } } } if (dis[n][0] == INF) // 如果终点的模k=0状态存在数字,那么就是说可以获取到最短路径,否则就是无法到达为个状态 cout << -1 << endl; else cout << dis[n][0] << endl; return 0; }