1. 程式人生 > 實用技巧 >ACwing 298 柵欄(單調佇列DP)

ACwing 298 柵欄(單調佇列DP)

題面

這道單調佇列優化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;
}