题解:P13515 [KOI 2025 #1] 木槿花开了
由题目可知:不可以在睁眼时在道路或有窗房内停留,而闭眼时在哪里都可以。问从点
Dijkstra 是一种求解非负权图上单源最短路径的算法。将结点分成两个集合:已确定最短路长度的点集(记为
S 集合)的和未确定最短路长度的点集(记为T 集合)。一开始所有的点都属于T 集合.初始化dis_𝑠=0 ,其他点的𝑑𝑖𝑠 均为+∞ 。然后重复这些操作:从𝑇 集合中,选取一个最短路长度最小的结点,移到𝑆 集合中。对那些刚刚被加入S 集合的结点的所有出边执行松弛操作。直到𝑇 集合为空,算法结束。——oi.wiki如果屋子有窗就不能在这里停留,否则可以在这里停留(当然也可以不停留)。从点
1 出发,先遍历所有点到点1 的最短路,再计算所有点到点n 的最短路。最后求出答案。
实现代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 10; // 最大建筑数
const long long INF = 0x3f3f3f3f3f3f3f3f; // 无穷大
long long n, m, c[N], A, B, dis[N];
// 原图:道路信息
struct edge { long long to, val; };
vector<edge> a1[N];// 原图邻接表
vector<edge> a2[N];// 新图:仅包含无窗户建筑之间的可达边
// Dijkstra 优先队列节点
struct node {
long long ix, d; // ix: 当前建筑编号, d: 到达该建筑的时间
bool operator<(node x) const { return d > x.d; } // 小根堆
};
/*
dis1(id): 从无窗户建筑 id(或 id 自身)出发,在原图上跑 Dijkstra。
限制:中间节点只能经过有窗户的建筑(c[x]==1),
即跳过除起点外的其他无窗户建筑作为中转。
这样得到的 dis[j] 就是从 id 到 j 且中间不经过其他无窗户建筑的最短距离。
*/
void dis1(long long id) {
memset(dis, 0x3f, sizeof(dis));
priority_queue<node> q;
dis[id] = 0;
q.push((node){id, 0});
while (!q.empty()) {
node qwq = q.top(); q.pop();
long long x = qwq.ix, d = qwq.d;
if (d > dis[x]) continue; // 已过时的状态,跳过
// 关键:如果不是起点,且当前建筑是无窗户的,则不能作为中转
if (x != id && !c[x]) continue;
for (long long i = 0; i < a1[x].size(); i++) {
long long y = a1[x][i].to, w = a1[x][i].val;
if (dis[y] > dis[x] + w) {
dis[y] = dis[x] + w;
q.push({y, dis[y]});
}
}
}
}
/*
dis2(): 在新图 a2(仅含无窗户建筑)上跑 Dijkstra。
考虑闭眼/睁眼周期来计算实际到达时间。
当前在节点 x,时间为 d,要走权值为 w 的边到 y:
r = d % (A+B) 表示当前时间在周期内的偏移量。
若 r + w <= A:当前闭眼周期内剩余时间足够走完这条边 -> 到达时间 d+w。
* 否则:需要等到下一个闭眼周期开始再出发 -> 到达时间 = 下一周期起点 + w。
*/
void dis2() {
memset(dis, 0x3f, sizeof(dis));
priority_queue<node> q;
dis[1] = 0;
q.push((node){1, 0});
while (!q.empty()) {
node qwq = q.top(); q.pop();
long long x = qwq.ix, d = qwq.d;
if (d > dis[x]) continue;
for (long long i = 0; i < a2[x].size(); i++) {
long long y = a2[x][i].to, w = a2[x][i].val;
long long r = d % (A + B); // 当前时刻在周期内的偏移
long long qqq; // 到达 y 的实际时间
if (r + w <= A) // 闭眼期内能走完
qqq = d + w;
else // 等下一个闭眼周期
qqq = (d / (A + B) + 1) * (A + B) + w;
if (dis[y] > qqq) {
dis[y] = qqq;
q.push({y, qqq});
}
}
}
}
int main() {
cin >> n >> m;
for (long long i = 1, a, b, c; i <= m; i++) {
cin >> a >> b >> c;
a1[a].push_back((edge){b, c}); // 原图:单向边
}
for (long long i = 1; i <= n; i++) cin >> c[i]; // 窗户信息
cin >> A >> B; // 闭眼/睁眼时长
// ---- 建新图 a2 ----
// 枚举每个无窗户建筑(不含 c[i]==1 的),计算其到其他无窗户建筑的可达性
for (long long i = 1; i <= n; i++) {
if (c[i]) continue; // 有窗户的建筑不能作为停留点
dis1(i); // 计算从 i 出发的最短距离
for (long long j = 1; j <= n; j++)
// i≠j,j 也无窗户,且一次闭眼周期内可达
if (i != j && !c[j] && dis[j] <= A)
a2[i].push_back({j, dis[j]});
}
// ---- 在新图上跑带周期约束的最短路 ----
dis2();
if (dis[n] == INF) cout << "-1";
else cout << dis[n];
return 0;
}
感谢观看。