狩猎(2021 CoE-II D)
metaphysis · · 题解
题目链接
本题的底层模型是无向图上的概率动态规划。
每个状态有三个参数:当前所在顶点
参考代码:
#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;
}