说明:这篇文章还有很多问题,没有整理好,但是我怕到时候忘了,所以先放上来,等到时候解决了再来更新这篇随笔。
题目链接:https://www.luogu.com.cn/problem/P3800
题目大意
一个 (N imes M) 个二维迷宫,上面有 (K) 个格子上面有宝物,这些宝物有对应的价值,
你一开始可以选择第一行的任意一个格子进入,然后每次你都会往下走一格,每次往下走一格,你都可以选择在水平方向向左或向右偏移若干个格子,但是最多只能偏移 (T) 个格子。
你需要寻找一格移动方案,是的从第一行的格子走到第 (N) 行的格子,获得宝物的价值之和最大,输出这个最大价值。
解题思路
其实一开始我想到的建图用SPFA求最长路。
对于第(i)、(j) 个宝物,只要 (x_i lt x_j) 且 (|y_i-y_j| ge |x_i-x_j| imes 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
", 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[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
", ans);
return 0;
}
但是这样的时间复杂度是 (O(N cdot M cdot K)),所以仍然会超时。
然后考虑使用 单调队列优化DP 。
然后本着不重复造轮子的思想,我要说:我所有的思路都完全来自下面这篇博客:
https://www.luogu.com.cn/blog/luckyblock/solution-p3800
按照上述思想,我们能够将时间复杂度优化到 (O(N cdot M))
实现代码如下: