洛谷P3800 Power收集 题解 单调队列优化DP(未完全整理好)

说明:这篇文章还有很多问题,没有整理好,但是我怕到时候忘了,所以先放上来,等到时候解决了再来更新这篇随笔。

题目链接:https://www.luogu.com.cn/problem/P3800

题目大意

一个 \(N \times M\) 个二维迷宫,上面有 \(K\) 个格子上面有宝物,这些宝物有对应的价值,
你一开始可以选择第一行的任意一个格子进入,然后每次你都会往下走一格,每次往下走一格,你都可以选择在水平方向向左或向右偏移若干个格子,但是最多只能偏移 \(T\) 个格子。
你需要寻找一格移动方案,是的从第一行的格子走到第 \(N\) 行的格子,获得宝物的价值之和最大,输出这个最大价值。

解题思路

其实一开始我想到的建图用SPFA求最长路。
对于第\(i\)\(j\) 个宝物,只要 \(x_i \lt x_j\)\(|y_i-y_j| \ge |x_i-x_j| \times T\),则从 \(i\) 就是可以到达 \(j\) 的,所以从 \(i\)\(j\) 连一条权值为 \(v_j\) 的边;
同时,因为所有的点都可能是我第一个到达的点,也都可能是我最后一个到达的点,所以我再设立一个超级源点 \(s\) 和超级汇点 \(t\) ,然后从 \(s\) 向所有的 \(i\) 连一条权值为 \(v_i\) 的边,从所有的 \(i\)\(t\) 连一条权值为 \(0\) 的边,然后从 \(s\)\(t\) 求最长路。

然后我因为是用的SPFA求最长路,所以时间复杂度为 \(O(V \cdot E) = O(K^3)\) ,果断超时。

TLE代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4040;
int N, M, K, T, s, t, x[maxn], y[maxn], v[maxn], dist[maxn];
struct Edge {
    int v, w;
    Edge () {};
    Edge (int _v, int _w) { v = _v; w = _w; }
};
vector<Edge> g[maxn];
queue<int> que;
bool inq[maxn];
void spfa() {
    memset(dist, -1, sizeof(dist));
    dist[s] = 0;
    que.push(s);
    while (!que.empty()) {
        int u = que.front();
        que.pop();
        inq[u] = false;
        int sz = g[u].size();
        for (int i = 0; i < sz; i ++) {
            int v = g[u][i].v, w = g[u][i].w;
            if (dist[v] == -1 || dist[v] < dist[u] + w) {
                dist[v] = dist[u] + w;
                if (!inq[v]) {
                    inq[v] = true;
                    que.push(v);
                }
            }
        }
    }
    printf("%d\n", dist[t]);
}
int main() {
    scanf("%d%d%d%d", &N, &M, &K, &T);
    for (int i = 0; i < K; i ++)
        scanf("%d%d%d", x+i, y+i, v+i);
    s = K, t = K+1;
    for (int i = 0; i < K; i ++) {
        g[s].push_back(Edge(i, v[i]));
        g[i].push_back(Edge(t, 0));
    }
    for (int i = 0; i < K; i ++) {
        for (int j = 0; j < K; j ++) {
            if (x[i] < x[j] && abs(y[i] - y[j]) <= T*abs(x[i] - x[j])) {
                g[i].push_back(Edge(j, v[j]));
            }
        }
    }
    spfa();
    return 0;
}

然后考虑使用 DP 来解决这个问题。

定义状态 \(v[i][j]\) 表示 \((i, j)\) 格子的宝物的价值(如果没有宝物则 \(v[i][j] = 0\)),
同时定义状态 \(f[i][j]\) 表示走到 \((i, j)\) 能够收获的最大价值,则:

\[f[i][j] = v[i][j] + \max_{k \in [j-T,j+T]} ( f[i-1][k] ) \]

而答案就是所有 \(f[N][i]\) 的最大值。

然后我们可以用滚动数组优化我们的 \(f[N][M]\) 的空间为 \(f[2][M]\)

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4040;
struct Node {
    int x, y, v;
} nodes[maxn];
int N, M, K, T, f[2][maxn], ans;
bool cmp(Node a, Node b) {
    return a.x < b.x || a.x==b.x && a.y < b.y;
}
int main() {
    scanf("%d%d%d%d", &N, &M, &K, &T);
    for (int i = 0; i < K; i ++) scanf("%d%d%d", &nodes[i].x, &nodes[i].y, &nodes[i].v);
    sort(nodes, nodes+K, cmp);
    int c = 0;
    for (int i = 1; i <= N; i ++) {
        for (int j = 1; j <= M; j ++) {
            int v = 0;
            if (nodes[c].x == i && nodes[c].y == j) v = nodes[c++].v;
            if (i == 1) f[i%2][j] = v;
            else {
                for (int k = max(1, j-T); k <= min(M, j+T); k ++)
                    f[i%2][j] = max(f[i%2][j], f[(i-1)%2][k] + v);
            }
        }
    }
    for (int i = 1; i <= M; i ++) ans = max(ans, f[N%2][i]);
    printf("%d\n", ans);
    return 0;
}

但是这样的时间复杂度是 \(O(N \cdot M \cdot K)\),所以仍然会超时。

然后考虑使用 单调队列优化DP

然后本着不重复造轮子的思想,我要说:我所有的思路都完全来自下面这篇博客:

https://www.luogu.com.cn/blog/luckyblock/solution-p3800

按照上述思想,我们能够将时间复杂度优化到 \(O(N \cdot M)\)

实现代码如下:


猜你喜欢

转载自www.cnblogs.com/quanjun/p/12636587.html