天地一抹红
题目链接:SSL 2401
题目大意
有一个 n*m 的网格,要从 (1,1) 走到 (n,m)。
然后你可以花费当前格的代价从 (i,j) 走到 (i+1,j),或者走到 (i,k) 其中 k>j。
当你走到 (i,k) 的时候,你可以选择 (i,j)~(i,k-1) 中地方含有宝石价值的最大值,然后就会给你贡献这个最大值乘 (i,k) 位置的法阵强度。
然后要你最大化最后走到 (n,m) 的贡献,如果无法非负就输出 -1。
思路
那肯定是一行一行的走,那行之间的转移只有一种而且很简单。
就直接
f
i
,
j
=
max
(
f
i
,
j
,
f
i
−
1
,
j
−
w
i
−
1
,
j
)
f_{i,j}=\max(f_{i,j},f_{i-1,j}-w_{i-1,j})
fi,j=max(fi,j,fi−1,j−wi−1,j)
然后考虑列的,那一个
O
(
m
2
)
O(m^2)
O(m2) 每次的转移就是:
f
i
,
j
=
max
(
f
i
,
j
,
max
k
=
1
j
−
1
f
i
,
k
−
w
i
,
k
+
V
i
,
k
,
j
−
1
a
i
,
j
)
f_{i,j}=\max(f_{i,j},\max\limits_{k=1}^{j-1}f_{i,k}-w_{i,k}+V_{i,k,j-1}a_{i,j})
fi,j=max(fi,j,k=1maxj−1fi,k−wi,k+Vi,k,j−1ai,j)
V
i
,
l
,
r
=
max
j
=
l
r
v
i
,
j
V_{i,l,r}=\max\limits_{j=l}^rv_{i,j}
Vi,l,r=j=lmaxrvi,j
那注意到特别的就是
a
i
,
j
⩾
a
i
,
j
+
1
a_{i,j}\geqslant a_{i,j+1}
ai,j⩾ai,j+1。
那按这么来说,考虑转移点,
f
i
,
k
−
w
i
,
k
f_{i,k}-w_{i,k}
fi,k−wi,k 越来越重要,
V
i
,
k
,
j
−
1
V_{i,k,j-1}
Vi,k,j−1 越来越不重要,那就其实有一个决策的单调性,可以用斜率优化 DP。
考虑改一下,固定
v
i
,
j
v_{i,j}
vi,j,然后前面的
f
i
,
k
−
w
i
,
k
f_{i,k}-w_{i,k}
fi,k−wi,k 是最大值,这个好处是可以直接顺着枚举过来的时候直接维护,因为维护的事
max
j
=
1
k
f
i
,
j
−
w
i
,
j
\max\limits_{j=1}^k f_{i,j}-w_{i,j}
j=1maxkfi,j−wi,j。
然后你队列维护单调递减的
V
V
V,然后这些地方作为转移点,然后斜率优化就行了。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N = 105;
const int M = 20005;
int n, m, F, sta[M];
ll w[N][M], h[N][M], a[N][M], f[N][M], g[M];
int re; char c;
int read() {
re = 0; c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
double clac(int i, int x, int y) {
return 1.0 * (g[y] - g[x]) / (h[i][x] - h[i][y]);
}
int main() {
freopen("red.in", "r", stdin);
freopen("red.out", "w", stdout);
n = read(); m = read(); F = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) w[i][j] = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) h[i][j] = read();
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) a[i][j] = read();
memset(f, -0x3f, sizeof(f)); f[0][1] = F;
for (int i = 1; i <= n; i++) {
f[i][1] = f[i - 1][1] - w[i - 1][1]; g[1] = f[i][1] - w[i][1];
int l = 1, r = 0; sta[++r] = 1;
for (int j = 2; j <= m; j++) {
while (l < r && clac(i, sta[l], sta[l + 1]) >= a[i][j]) l++;
f[i][j] = max(f[i - 1][j] - w[i - 1][j], g[sta[l]] + h[i][sta[l]] * a[i][j]);
g[j] = max(g[j - 1], f[i][j] - w[i][j]);
while (l <= r && h[i][j] >= h[i][sta[r]]) r--;
while (l < r && clac(i, sta[r - 1], j) >= clac(i, sta[r - 1], sta[r])) r--;
sta[++r] = j;
}
}
if (f[n][m] >= 0) printf("%lld", f[n][m]);
else printf("-1");
return 0;
}