LuoguP3953 逛公园(最短路+dp)

逛公园

题目:

策策同学特别喜欢逛公园。公园可以看成一张 \(N\) 个点 \(M\) 条边构成的有向图,且没有 自环和重边。其中 \(1\) 号点是公园的入口,\(N\) 号点是公园的出口,每条边有一个非负权值, 代表策策经过这条边所要花的时间。
策策每天都会去逛公园,他总是从 \(1\) 号点进去,从 \(N\) 号点出来。策策喜欢新鲜的事物,它不希望有两天逛公园的路线完全一样,同时策策还是一个 特别热爱学习的好孩子,它不希望每天在逛公园这件事上花费太多的时间。如果 \(1\) 号点 到 \(N\) 号点的最短路长为 \(d\),那么策策只会喜欢长度不超过 \(d + K\) 的路线。
策策同学想知道总共有多少条满足条件的路线,你能帮帮它吗?为避免输出过大,答案对 \(P\) 取模。如果有无穷多条合法的路线,请输出 \(-1\)

思路:

因为题目保证了从 \(1\)\(N\) 一定是可以到达的,而且同时还要知道最短路的长 \(d\) ,就不难想到要跑一遍最短路,但是我们要知道其他能够到达 \(n\) 的方案的路径长度,所以连边的时候不能够用无向边来存储,需要建两个图(正图,反图), 正图用来跑出来 \(1 \rightarrow N\) 的最短路的长度,反图用来处理相差不超过 \(K\) 的方案。

    int n, m, k, p;
    std::cin >> n >> m >> k >> p;
    //建出正反图
    std::vector<std::vector<std::array<int, 2>>> g1(n + 1);
    std::vector<std::vector<std::array<int, 2>>> g2(n + 1);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        std::cin >> u >> v >> w;
        g1[u].push_back({v, w});
        g2[v].push_back({u, w});
    }

    // Dijkstra算法
    std::priority_queue<std::array<int, 2>, std::vector<std::array<int, 2>>, std::greater<std::array<int, 2>>> q;
    q.push({0, 1});
    std::vector<int> dist(n + 1, 1E9);
    std::vector<bool> vis(n + 1);
    dist[1] = 0;
    while(q.size()) {
        auto [w, u] = q.top(); q.pop();
        if (vis[u]) continue;
        vis[u] = true;

        for (auto& [v, ww] : g1[u]) {
            if (dist[v] > dist[u] + ww) {
                dist[v] = dist[u] + ww;
                q.push({dist[v], v});
            }
        }
    }

由于最终要求的是方案数,并且同样有相差不超过 \(K\) 的额外限制,所以考虑用 \(dp\) 来求解方案数。定义状态为 \(dp[u][k]\) 代表从 \(1 \rightarrow u\) 的路径中长度为 \(dist_u + k\) 的方案数。现在考虑状态的转移,假设现在要从 \(u \rightarrow v\) ,也就是 \(dp[u][k] \rightarrow dp[v][x]\) 也就是 \(dist_v + x + w(u, v) = dist_u + k \Rightarrow x = dist_u - dist_v + k - w(u, v)\) 。这样 \(dp[u][k]\) 就可以从 \(dp[v][dist_u - dist_v + k - w(u, v)]\) 转移过来。在反图上跑一遍 \(dp\) ,用记忆化搜索的方式会比较好写,

    std::vector<std::vector<bool>> st(n + 1, std::vector<bool> (k + 1));
    std::vector<std::vector<int>> dp(n + 1, std::vector<int> (k + 1));

    int f = 0;
    std::function<int(int, int)> dfs = [&](int u, int now) -> int {
        if (now < 0) return 0;
        if (st[u][now]) { f = 1; return 0; }
        if (dp[u][now]) return dp[u][now];
        st[u][now] = true;

        int ans = 0;
        for (auto& [v, w] : g2[u]) {
            ans = (ans + dfs(v, dist[u] - dist[v] + now - w)) % p;
            if (f) return 0;
        }
        st[u][now] = false;
        return dp[u][now] = ans;
    };

    dfs(1, 0);
    dp[1][0] = 1;
    int ans = 0;
    for (int i = 0; i <= k; i++) {
        ans = (ans + dfs(n, i)) % p;
    }

    if (f == 1) return void(std::cout << "-1\n");
    std::cout << ans << "\n";
posted @ 2022-10-15 16:20  浅渊  阅读(27)  评论(0)    收藏  举报