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_{j - 1} - g_{i} - 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; }View Code
Luogu 1070 道路遊戲