1 条题解

  • 0
    @ 2025-8-24 22:31:21

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar metaphysis
    故不积跬步,无以至千里;不积小流,无以成江海。——《荀子·劝学篇》

    搬运于2025-08-24 22:31:21,当前版本为作者最后更新于2021-03-02 08:27:13,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目链接

    本题的底层模型是无向图上的概率动态规划。

    每个状态有三个参数:当前所在顶点 uu,已经消耗的体力 hh,已经花费的时间 tt。令 hp[u][h][t]hp[u][h][t] 表示在顶点 uu,已经消耗的体力为 hh,已经花费的时间为 tt 时消耗体力的期望值(即平均值),elapsed[u][h][t]elapsed[u][h][t] 表示在顶点 uu,已经消耗的体力为 hh,已经花费的时间为 tt 时花费时间的期望值(即平均值),根据题目给定的规则进行状态转移,所求即 hp[0][0][0]hp[0][0][0]elapsed[0][0][0]elapsed[0][0][0]。读者可结合参考代码的注释进行理解。

    参考代码:

    #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
    上传者