ACwing 298 柵欄(單調佇列DP)
阿新 • • 發佈:2020-07-14
這道單調佇列優化DP非常好。閱讀完題後我們首先可以知道,要讓這個序列有序,我們需要先按照升序對木匠必須粉刷的板子進行排序。
這樣之後我們可以設定出一個很顯然的狀態f[i][j]表示:前i個木匠,處理完了前j塊木板的最大值(處理不一定都刷)
在處理的時候我們應用最典型的線性DP模板,從0 - j 中選出一個k進行從上一個狀態,即f[i-1][k]轉移過來,此時的狀態轉移方程為
f[i][j] = max{f[i-1][k] + p[i] * (j - k),f[i-1][j], f[i][j-1]}//對應這個木匠不刷,和這個木匠不刷j,只刷到j-1
複雜度為O(N^2 * M)
我們發現在這個式子中 J 是不斷單調像右的,而 K 出於 S[i] 單調遞增, k >= s[i] - l[i] ,所以也是單調遞增的,故可以維護單調佇列。
值得注意的是單調佇列維護的是上一個狀態的範圍的值,所以理應可以想到 K (或者說是J) 一定要 < S[i] 可以在程式碼裡仔細體會
所以上個狀態單調佇列的範圍就是 s[i] - 1, s[i] - 1 - l[i], 當前狀態的取值範圍為 s[i] 到 s[i] + l[i]
程式碼很有意思,多多琢磨
程式碼:
#include <bits/stdc++.h> using namespace std; int n, m, ans; int f[105][16005]; struct node{ int l; int p; int s; }a[105]; int q[16005]; bool cmp(node i,node j){ return i.s < j.s; } int main(){ scanf("%d %d", &n, &m); for(int i = 1;i <= m;++ i){ scanf("%d %d %d", &a[i].l, &a[i].p, &a[i].s); } sort(a + 1,a + m + 1, cmp); for(int i = 1;i <= m;++ i){ int h = 0,t = -1; for(int j = 0;j <= n;++ j){ f[i][j] = f[i-1][j]; if(j) f[i][j] = max(f[i-1][j], f[i][j-1]); if(h <= t && q[h] < j - a[i].l) h ++; if(j < a[i].s){//可以考慮入隊了,佇列只寸編號 while(h <= t && f[i-1][q[t]] + a[i].p * (j - q[t]) <= f[i-1][j] + (j - j) * a[i].p) t --; q[++ t] = j; } if(j >= a[i].s && h <= t && j <= a[i].s + a[i].l){ f[i][j] = max(f[i][j], f[i-1][q[h]] + (j - q[h]) * a[i].p); } } } cout << f[m][n]; return 0; }