若“松弛”和“何时定案”还陌生,先完成 课前零食:从乱走到松弛。
学完后,你应能把“达到状态的最小代价”建成图,按边权选择 BFS、0-1 BFS 或 Dijkstra,解释松弛不变量,并通过扩展状态处理免费切换等全局模式。
前置:队列、优先队列、无向/有向图、long long。
先问“状态是什么、一次合法动作是什么、动作代价是什么”,再选择算法:
| 边权 | 算法 | 复杂度 |
|---|---|---|
| 全部为 1 | BFS | $O(V+E)$ |
| 只有 0/1 | 0-1 BFS | $O(V+E)$ |
| 全部非负 | 堆优化 Dijkstra | $O((V+E)\log V)$ |
| 含负边 | Bellman-Ford / DAG DP 等 | 见差分约束章节 |
Dijkstra 维护 dist[v] 为当前已知上界;每次取未确定点中距离最小者。因所有未走边非负,任何绕过其他未确定点的路线都不会更短,所以该距离可最终确定。负边会破坏这一论证。
松弛 dist[v] > dist[u] + w 的含义是发现一个更好的可行上界。BFS、0-1 BFS 与 Dijkstra 的差别主要在于按什么顺序取出待扩展状态;这个顺序必须保证状态被最终确定时,不会再有尚未处理的路径把它变得更小。
numeric_limits<long long>::max()/4,避免 INF+w 溢出。(distance, vertex);弹出后检查 if (d != dist[v]) continue。visited 阻止一个尚未弹出的点被再次松弛。有 $2\le N\le2\times10^5$ 个关卡。对每个 $i<N$,花 $A_i$ 可开放 $i+1$,或花 $B_i$ 可开放 $X_i$;费用至 $10^9$。求从可玩关卡 1 到可玩关卡 $N$ 的最小总时间。
对每个 $i<N$ 加边 $i\to i+1$ 权 $A_i$、$i\to X_i$ 权 $B_i$,从 1 运行 Dijkstra。任一游戏动作序列与图上一条路径一一对应且代价相同,所以最短路径就是最少时间。复杂度 $O(N\log N)$,距离用 long long。
for each stage i:
add edge i -> i+1 with cost A[i]
add edge i -> X[i] with cost B[i]
dist = Dijkstra(graph, source=1)
output dist[N]
完整 GNU C++17 参考实现见附件:AtCoder-abc340_d.cpp
若对所有 $i$ 都有 $X_i>i$,图是 DAG,可按编号或拓扑序 DP 去掉对数;本题未给该条件,不能擅自使用。
无向图有 $N,M\le2\times10^5$。每条边标记 0/1,初始仅 1 边可走;在指定的 $K$ 个点可免费按开关,使全图 0/1 通行性翻转。求从 1 到 $N$ 的最少移动次数,不能到达输出 -1。图可有重边或不连通。
建立两层点 $(v,0),(v,1)$。原边标签 $a$ 只在第 $a$ 层连接两个端点,代价 1;开关点的两层状态互连,代价 0。初态 $(1,1)$,答案是终点两层距离较小者。
任何真实行动序列都对应状态图路径,反之每条状态边都是合法移动或按键;路径权只累计 Move 次数。因此 0-1 BFS 最短权即答案。扩展规模 $2N$ 点、$M+2K$ 方向边(每条原边只连到一层 a;$K$ 个开关点贡献 $2K$ 条层间边),复杂度 $O(N+M+K)$。
state = (vertex, currently_passable_edge_label)
for original edge with label a: connect its endpoints in layer a with cost 1
for each switch vertex: connect its two layers with cost 0
dist = zero_one_BFS(start=(1, label_1))
output min(dist[(N,0)], dist[(N,1)])
完整 GNU C++17 参考实现见附件:AtCoder-abc277_e.cpp
若按开关也计一次操作,层间边权改为 1,普通 BFS 即可;若不同开关费用各异且非负,换回 Dijkstra。
$n\le10^5,m\le3\times10^5$ 的连通带权无向道路图,首都为 1;另有 $k\le10^5$ 条从首都到某城市的火车,可重复到同一城。删尽量多火车,同时保持首都到所有城市的最短距离不变。
先对同终点火车取最小值作为 Dijkstra 初值,连同首都 0 在道路图上松弛,得到使用全部火车时的最短距离。扫描道路,若某方向满足等式,标记终点有道路前驱。
正权保证沿道路最短前驱距离严格下降,最终会到首都或某个必须保留火车的城市,不会形成自给自足的零权环。因此每个没有道路前驱、且最短火车等于最终距离的城市必须且只需留一条;其余火车全删。答案为 $k-\text{needed}$。复杂度 $O((n+m+k)\log n)$。
train[v] = minimum train length to v
run road-only Dijkstra with seeds (capital,0) and every finite train[v]
mark v if some road u-v satisfies dist[u] + w == dist[v]
needed = count(v where train[v] == dist[v] and v has no road predecessor)
output total_trains - needed
完整 GNU C++17 参考实现见附件:CodeForces-449B.cpp
若道路允许零权,最短前驱可能形成等距环,单个 road_predecessor 不足以判断一整块是否仍需火车;需先压缩零权等距关系或在最短路子图上分析来源。
先独立完成第一题;第二题先画两层图再编码;第三题至少构造“同城多条同长火车”“火车与道路同长”“火车先到中间城再走道路”三组测试。复盘时记录你是在选择算法前还是后才明确状态。