Luogu 1070 道路游戏
看完题面想了一会发现只会写$n^3$,愣了一会才想出了单调队列优化的做法。
90分算法:
设$f_{i, j, k}$表示第$i$分钟在第$j$座城市已经走了$k$步的最大价值,转移显然,时间复杂度$O(n^3)$。
但是我没有实现它。
100分算法:
思考一下最终的答案是怎样选出来的,我们把矩阵画出来:
发现题目可以转化为在这个矩阵上选若干个斜对角线,使得每一列都有且仅有一个被选到,最后获得的总价值为所有选到的格子上的值再减去所有斜对角线开始的格子对应的代价,使这个总价值最大。
设$f_{i, j}$表示到第$i$分钟,当前再$j$能选到的最大价值,那么发现最多只能从$1-p$层上转移过来。
$f_{i, j} = max(max(f_{i - k,o}) + calc(i- k, i) - cost_{pos(j, k)}) (1 \leq k \leq p - 1) (1 \leq o \leq n)$
$calc(i, j)$表示这一条对角线(从$i$层到$j$层)上的价值总和,可以通过斜线上预处理前缀和得到,$pos(i, j)$表示$i$上移$j$层所到达的纵坐标,$cost_{i}$表示在第$i$个城市买机器人的代价。
那么对每一条斜的对角线维护一个单调队列即可。
时间复杂度$O(n^2)$。
我还是没有实现它。
介绍一种dalao的思路。(第二个id叫Ghastlcon)的大佬,他的Luogu博客中这篇文章看不了了……
有一个小trick就是把下标从$0$到$n - 1$编号,这样子上面定义的$pos(i, j) = ((i - j) \% n + n) \% n$,这样子做前缀和的时候也比较方便。
其实我们发现$f$的第二维是没r用的,所以可以直接拿掉,因为在一个结束的位置并不影响在下一个开始的位置,我们要的只是这个最大值。
把前缀和$g$完整地写出来,有:$f_{i} = max(f_{j} + g_{i} - g_{j - 1} - cost_{j}) (1 \leq i - j \leq p - 1)$。
可以把之后与$i$有关的项拿到外面来,就可以用单调队列优化了。
时间复杂度$O(n^2)$。
Code:
#include <cstdio> #include <cstring> using namespace std; const int N = 1005; int n, m, k, a[N], g[N][N], f[N]; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9'|| ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } struct PriQueue { int lst[N], pos[N], l, r; inline void init() { l = 1, r = 0; } inline void push(int x, int val) { for(; l <= r && lst[r] < val; --r); lst[++r] = val, pos[r] = x; } inline void pop(int p) { for(; l <= r && pos[l] < p; ++l); } } q[N]; inline int pos(int x, int len) { return ((x - len) % n + n) % n; } inline void chkMax(int &x, int y) { if(y > x) x = y; } int main() { read(n), read(m), read(k); for(int i = 0; i < n; i++) for(int j = 1; j <= m; j++) read(g[i][j]); for(int i = 0; i < n; i++) read(a[i]); for(int j = 2; j <= m; j++) for(int i = 0; i < n; i++) g[i][j] += g[(i + n - 1) % n][j - 1]; for(int i = 0; i < n; i++) q[i].init(); for(int i = 0; i < n; i++) { int now = pos(i, -1); q[now].push(0, -a[i]); } memset(f, 128, sizeof(f)); f[0] = 0; for(int i = 1; i <= m; i++) { for(int j = 0; j < n; j++) { int now = pos(j, i - 1); chkMax(f[i], q[now].lst[q[now].l] + g[pos(j, 1)][i]); } for(int j = 0; j < n; j++) { int now = pos(j, i - 1); q[now].pop(i - k + 1); q[now].push(i, f[i] - g[pos(j, 1)][i] - a[j]); } } printf("%d\n", f[m]); return 0; }