1 条题解
-
0
自动搬运
来自洛谷,原作者为

metaphysis
故不积跬步,无以至千里;不积小流,无以成江海。——《荀子·劝学篇》搬运于
2025-08-24 22:31:21,当前版本为作者最后更新于2021-03-02 08:27:13,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
本题的底层模型是无向图上的概率动态规划。
每个状态有三个参数:当前所在顶点 ,已经消耗的体力 ,已经花费的时间 。令 表示在顶点 ,已经消耗的体力为 ,已经花费的时间为 时消耗体力的期望值(即平均值), 表示在顶点 ,已经消耗的体力为 ,已经花费的时间为 时花费时间的期望值(即平均值),根据题目给定的规则进行状态转移,所求即 ,。读者可结合参考代码的注释进行理解。
参考代码:
#include <bits/stdc++.h> using namespace std; const int INF = 0x7f7f7f7f; // 表示无向图中边的数据结构,v 为邻接顶点的编号,h 为消耗的体力,t 为花费的时间 struct edge { int v, h, t; edge (int v = 0, int h = 0, int t = 0): v(v), h(h), t(t) {} // 重载小于运算符以使用优先队列 bool operator<(const edge &e) const { return t != e.t ? t > e.t : h > e.h; } }; // p[i] 表示第 i 个狩猎点捕获猎物的概率, // hp[i][j][k] 和 elapsed[i][j][k] 的意义如前所述 double p[210], hp[210][260][260], elapsed[210][260][260]; // visited 为标记数组,标记某个状态是否已经访问 int visited[210][260][260]; vector<edge> edges[210]; // dH[i] 表示从地点 i 回到巢穴的符合要求的路径所消耗的体力 // dT[i] 表示从地点 i 回到巢穴的符合要求的路径所花费的时间 // neighbours[i] 表示顶点 i 所邻接的狩猎点个数 // hi[i] 表示在第 i 个地点进行一次捕猎所消耗的体力 // ti[i] 表示在第 i 个地点进行一次捕猎所花费的时间 // 很明显,在巢穴无法进行捕猎,故 hi[0] = ti[0] = 0 int N, M, H, T, dH[210], dT[210], neighbours[210], hi[210], ti[210]; // u 为当前所在的顶点,h 为已经消耗的体力,t 为已经花费的时间 void dfs(int u, int h, int t) { // 备忘以加快求解速度,否则会超时 if (visited[u][h][t]) return; visited[u][h][t] = 1; hp[u][h][t] = elapsed[u][h][t] = 0; // 如果当前是狩猎点 if (u) { // 有 p[u] 的概率在该狩猎点捕猎成功,直接返回巢穴结束狩猎 hp[u][h][t] += p[u] * (h + dH[u]); elapsed[u][h][t] += p[u] * (t + dT[u]); // 若不成功,检查体力和时间是否超出限制,如果超出,直接选择返回巢穴 if (h >= H || t >= T) { hp[u][h][t] += (1 - p[u]) * (h + dH[u]); elapsed[u][h][t] += (1 - p[u]) * (t + dT[u]); return; } // 当狩猎不成功且体力和和时间均未超出限制时, // 如果当前狩猎点与其他狩猎点无直连双向道路则 // 返回巢穴,更新期望体力和时间,注意是转移到 // 巢穴后再更新 if (!neighbours[u]) { dfs(0, h + dH[u], t + dT[u]); hp[u][h][t] += (1 - p[u]) * hp[0][h + dH[u]][t + dT[u]]; elapsed[u][h][t] += (1 - p[u]) * elapsed[0][h + dH[u]][t + dT[u]]; } // 当前狩猎点与其他狩猎点有直连双向道路,随机选择一个狩猎点进行状态转移 else { double s = 1.0 / neighbours[u]; for (auto e : edges[u]) { // 确保是狩猎点而不是巢穴 if (e.v) { dfs(e.v, h + e.h + hi[e.v], t + e.t + ti[e.v]); hp[u][h][t] += s * (1 - p[u]) * hp[e.v][h + e.h + hi[e.v]][t + e.t + ti[e.v]]; elapsed[u][h][t] += s * (1 - p[u]) * elapsed[e.v][h + e.h + hi[e.v]][t + e.t + ti[e.v]]; } } } } // 当前位于巢穴 else { // 体力或者时间超出限制 if (h >= H || t >= T) { hp[u][h][t] = h + dH[u]; elapsed[u][h][t] = t + dT[u]; return; } // 如果有狩猎点与巢穴相邻,随机选择一个狩猎点进行状态转移 if (neighbours[u]) { double s = 1.0 / neighbours[u]; for (auto e : edges[u]) { dfs(e.v, h + e.h + hi[e.v], t + e.t + ti[e.v]); hp[u][h][t] += s * hp[e.v][h + e.h + hi[e.v]][t + e.t + ti[e.v]]; elapsed[u][h][t] += s * elapsed[e.v][h + e.h + hi[e.v]][t + e.t + ti[e.v]]; } } } } int main(int argc, char *argv[]) { cin.tie(0), cout.tie(0), ios::sync_with_stdio(false); cin >> N; for (int i = 1; i <= N; i++) cin >> hi[i] >> ti[i] >> p[i]; for (int i = 0; i <= N; i++) { edges[i].clear(); neighbours[i] = 0; } cin >> M; for (int i = 0, u, v, h, t; i < M; i++) { cin >> u >> v >> h >> t; edges[u].push_back(edge(v, h, t)); edges[v].push_back(edge(u, h, t)); // 确定每个顶点有多少个狩猎点与之相邻 neighbours[u] += (v > 0); neighbours[v] += (u > 0); } cin >> H >> T; // 确定每个地点返回巢穴时具有最短时间的路径, // 如果有多条具有最短时间的路径,选择消耗体力 // 最少的路径 memset(dH, INF, sizeof dH); memset(dT, INF, sizeof dT); priority_queue<edge> q; dH[0] = dT[0] = 0; q.push(edge(0, 0, 0)); while (!q.empty()) { edge e1 = q.top(); q.pop(); if (dT[e1.v] < e1.t) continue; for (auto e2 : edges[e1.v]) { if (dT[e2.v] > dT[e1.v] + e2.t || (dT[e2.v] == dT[e1.v] + e2.t && dH[e2.v] > dH[e1.v] + e2.h)) { dT[e2.v] = dT[e1.v] + e2.t; dH[e2.v] = dH[e1.v] + e2.h; q.push(edge(e2.v, dH[e2.v], dT[e2.v])); } } } // 递归加备忘确定期望值 memset(visited, 0, sizeof(visited)); dfs(0, 0, 0); cout << fixed << setprecision(1) << hp[0][0][0] << ' ' << elapsed[0][0][0] << '\n'; return 0; }
- 1
信息
- ID
- 6524
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者