通过 Floyd 预处理任意两教室间的最短路,并使用动态规划求出在至多更换 $m$ 节课程限制下,移动消耗体力的期望最小值。
OJ: roj
题目 ID: 3640
难度:提高+/省选-
标签:动态规划概率期望最短路
创建: 2026-10-10 09:20
更新: 2026-10-10 09:20
形式化题目
有
你至多可以对
求在最优的申请策略下,这
正解
思路
首先由于每次下课后从一间教室走到下一间教室走的必然是最短路径,而且教室数
接下来是一个比较典型的线性动态规划。
在所有的
可以利用滚动数组优化掉第一维
若当前课不申请(
- 若上一节课未申请:体力增加为
- 若上一节课申请了:有
概率在 上课,有 概率仍在 上课。 体力期望增加为
若当前课申请(
- 若上一节未申请:
体力期望增加为
- 若上一节申请了:有 4 种组合情况,分别为都成功、前成功后失败、前失败后成功、都失败。
体力期望增加为:
每一轮迭代后保留期望值的最小值即可,由于转移的合法性,初始状态仅
代码
import sys
def main():
input = sys.stdin.read
data = input().split()
if not data:
return
n, m, v, e = map(int, data[:4])
idx = 4
c = [0] + [int(x) for x in data[idx:idx+n]]
idx += n
d = [0] + [int(x) for x in data[idx:idx+n]]
idx += n
k = [0.0] + [float(x) for x in data[idx:idx+n]]
idx += n
dis = [[float('inf')] * (v + 1) for _ in range(v + 1)]
for i in range(1, v + 1):
dis[i][i] = 0
for _ in range(e):
u = int(data[idx])
v_node = int(data[idx+1])
w = int(data[idx+2])
idx += 3
if w < dis[u][v_node]:
dis[u][v_node] = dis[v_node][u] = w
for p in range(1, v + 1):
for i in range(1, v + 1):
if dis[i][p] != float('inf'):
for j in range(1, v + 1):
if dis[i][p] + dis[p][j] < dis[i][j]:
dis[i][j] = dis[i][p] + dis[p][j]
f = [[float('inf')] * 2 for _ in range(m + 1)]
f[0][0] = 0.0
if m >= 1:
f[1][1] = 0.0
for i in range(2, n + 1):
nf = [[float('inf')] * 2 for _ in range(m + 1)]
c_prev, d_prev, k_prev = c[i-1], d[i-1], k[i-1]
c_curr, d_curr, k_curr = c[i], d[i], k[i]
for j in range(min(i, m) + 1):
v1 = f[j][0] + dis[c_prev][c_curr]
v2 = f[j][1] + k_prev * dis[d_prev][c_curr] + (1.0 - k_prev) * dis[c_prev][c_curr]
nf[j][0] = min(v1, v2)
if j > 0:
v3 = f[j-1][0] + k_curr * dis[c_prev][d_curr] + (1.0 - k_curr) * dis[c_prev][c_curr]
v4 = f[j-1][1] + \
k_prev * k_curr * dis[d_prev][d_curr] + \
k_prev * (1.0 - k_curr) * dis[d_prev][c_curr] + \
(1.0 - k_prev) * k_curr * dis[c_prev][d_curr] + \
(1.0 - k_prev) * (1.0 - k_curr) * dis[c_prev][c_curr]
nf[j][1] = min(v3, v4)
f = nf
ans = min(min(f[j][0], f[j][1]) for j in range(m + 1))
print(f"{ans:.2f}")
if __name__ == '__main__':
main()#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
typedef long long ll;
const int MAXN = 2005;
const int MAXV = 305;
const double INF = 1e18;
int n, m, v, e;
int c[MAXN], d[MAXN];
double k[MAXN];
int dis[MAXV][MAXV];
double f[MAXN][2];
double nf[MAXN][2];
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
if (!(cin >> n >> m >> v >> e)) return 0;
for (int i = 1; i <= n; ++i) {
cin >> c[i];
}
for (int i = 1; i <= n; ++i) {
cin >> d[i];
}
for (int i = 1; i <= n; ++i) {
cin >> k[i];
}
for (int i = 1; i <= v; ++i) {
for (int j = 1; j <= v; ++j) {
if (i == j) dis[i][j] = 0;
else dis[i][j] = 1e8;
}
}
for (int i = 0; i < e; ++i) {
int u, v_node, w;
cin >> u >> v_node >> w;
if (w < dis[u][v_node]) {
dis[u][v_node] = w;
dis[v_node][u] = w;
}
}
for (int p = 1; p <= v; ++p) {
for (int i = 1; i <= v; ++i) {
for (int j = 1; j <= v; ++j) {
if (dis[i][p] + dis[p][j] < dis[i][j]) {
dis[i][j] = dis[i][p] + dis[p][j];
}
}
}
}
for (int j = 0; j <= m; ++j) {
f[j][0] = f[j][1] = INF;
}
f[0][0] = 0.0;
if (m >= 1) {
f[1][1] = 0.0;
}
for (int i = 2; i <= n; ++i) {
for (int j = 0; j <= m; ++j) {
nf[j][0] = nf[j][1] = INF;
}
for (int j = 0; j <= min(i, m); ++j) {
// 第 i 节课不申请
double v1 = f[j][0] + dis[c[i-1]][c[i]];
double v2 = f[j][1] + k[i-1] * dis[d[i-1]][c[i]] + (1.0 - k[i-1]) * dis[c[i-1]][c[i]];
nf[j][0] = min(v1, v2);
// 第 i 节课申请
if (j > 0) {
double v3 = f[j-1][0] + k[i] * dis[c[i-1]][d[i]] + (1.0 - k[i]) * dis[c[i-1]][c[i]];
double v4 = f[j-1][1] +
k[i-1] * k[i] * dis[d[i-1]][d[i]] +
k[i-1] * (1.0 - k[i]) * dis[d[i-1]][c[i]] +
(1.0 - k[i-1]) * k[i] * dis[c[i-1]][d[i]] +
(1.0 - k[i-1]) * (1.0 - k[i]) * dis[c[i-1]][c[i]];
nf[j][1] = min(v3, v4);
}
}
for (int j = 0; j <= m; ++j) {
f[j][0] = nf[j][0];
f[j][1] = nf[j][1];
}
}
double ans = INF;
for (int j = 0; j <= m; ++j) {
ans = min(ans, f[j][0]);
ans = min(ans, f[j][1]);
}
cout << fixed << setprecision(2) << ans << "\n";
return 0;
}复杂度
- 时间复杂度:计算最短路 Floyd
。动态规划状态数为 ,每次转移常数时间 。故总时间复杂度为 。其中 ,能够毫无压力地在时限内跑完。 - 空间复杂度:存储地图最短路
,DP 使用了滚动数组空间复杂度为 或不滚动 ,都非常优秀。总空间复杂度为 。
总结
- 图中会有重边、自环,需要在建图时对同样两点的距离取最小值。
- 初始化地图最短距离为无穷大,注意不要使用太大的无穷大造成溢出。DP 的初值同样应为一个较大的数。
- 概率转移一定要推导仔细,所有概率系数相加恒等于 1,保证期望和正确。输出要注意四舍五入保留两位小数。
★★ 数据目录污染记录(T78,数据缺陷第 24 类)
new_ROJ/problems/3640/data/ 原本含 两套互不相同的数据:
| 序列 | 数量 | 首行形态 | 示例答案 | 闸门 |
|---|---|---|---|---|
classroom1..25.in/.ans |
25 对 | 1 1 300 34973(4 整数 + 多行浮点) |
0.00 |
PASS 25/25 |
★ problem1..20.in/.ans |
20 对 | 1 2 / 1 1(图的边) |
0 |
FAIL 20/20 |
机械判据(决定性)
原闸门结果 恰好是 PASS=25, FAIL=20:
= 「classroom 序列【全过】」+ 「problem 序列【全败】」
⇒ 这不是「算法在 20 个点上错了」,而是【数据分两套】的指纹★ 判据模式:当失败数恰好等于某个文件序列的长度时, 应怀疑「目录里有多个序列」,而不是「算法部分正确」。
处置
- ★ 不做「让 20 个异题点通过」的努力(那是
T62:不该反解) - 异题文件已移出
data/,备份在new_ROJ/problems/_foreign-3640/(40 个文件) - 移出后闸门 PASS=25/25(
main.cpp与main.py双通过)⇒ 证实主解对本題序列完全正确
与 T41 的关系
| 编号 | 缺陷 |
|---|---|
T41 |
题面与数据是两道不同的题(整个数据都是异题的) |
T78 |
★ 数据目录【混入】异题数据(部分污染) |
⇒ T78 更隐蔽:一部分点会通过,看起来像「算法不够好」。

