1. 程式人生 > >【單調隊列優化dp】 分組

【單調隊列優化dp】 分組

bubuko 單調隊列優化 定義 sin 求最大值 出隊 getchar() tdi 變化

【單調隊列優化dp】 分組

>>>>題目

【題目】

給定一行n個非負整數,現在你可以選擇其中若幹個數,但不能有連續k個數被選擇。你的任務是使得選出的數字的和最大

【輸入格式】

第一行兩個整數 n,k,如題目描述
接下來一行n 個數,表示這個序列

【輸出格式】

輸出一行一個數,表示最大的和

【輸入樣例】

5 2
1 2 3 4 5

【輸出樣例】

12

【數據範圍與約定】

對於20%的數據,保證1 <=n <=10。

對於40%的數據,保證1 <=n <=200。

對於60%的數據,保證1 <=n <=100000。

對於100%的數據,保證1 <=n <=2000000,1<=K<=n。

>>>>分析

因為題目給出非負整數,根據貪心,我們盡量多選擇長度為k的序列

數據範圍很大,暴力求每一段的和的做法肯定會T掉,那麽考慮dp

題目涉及到求區間和,我們預處理出前綴和,用O(1)復雜度求出區間和

定義:dp[i]表示前i個人的最大收益,sum[i]表示前綴和

現在選的這一段區間和用sum[i]-sum[j]表示(i-k<=j<=i)

這段區間與 前一段長為k的區間 中間要空一個數(不能連續選k+1個數),於是我們固定i點,不斷地枚舉j點,找到和的最大值

因為sum求的是閉區間的前綴和 , sum[i]-sum[i]表示區間[ j+1 ,i ]的和,中間空的數就是j,再加上dp[j-1]就可以更新dp[i]的值

技術分享圖片

綜上,我們可以得到狀態轉移方程

dp[i]=max( dp[j-1]+sum[i]-sum[j]) (i-k<=j<=i)

這裏的max是對應每一個j點算出括號裏的值,再求最大值

那麽又怎麽算最大值呢?總不能真的枚舉j再找最大值吧?肯定不行,時間復雜度太高

但是你會突然發現

原方程可以拆分成 dp[i]=max(dp[j-1]-sum[j])+sum[i]

然後我們可以發現這個式子的變化與sum[i]無關,因為我們固定了i點,只是j點在變

那麽我們考慮一個超棒的家夥——單調隊列優化

(註意一下單調隊列裏面存的是下標)

定義f[j]=dp[j-1]-sum[j],將f[j]丟進單調隊列裏面,每次取出隊首元素

擴展下一個點的時候,更新一下:f[i+1]=dp[i]-sum[i+1](和上面的式子是一樣的),將它丟進單調隊列裏面就好啦

ヾ(????)?"

>>>>代碼

#include<bits/stdc++.h>
#define maxn 2000005
#define ll long long
using namespace std;
int head=0,tail=1,n,k;
ll dp[maxn],f[maxn],sum[maxn];
int a[maxn],q[maxn];
int read()
{
    int x=0 ; char c=getchar() ;
    while(c<0||c>9) c=getchar() ;
    while(c>=0&&c<=9) { x=x*10+c-0, c=getchar() ; }
    return x;
}
int main()
{
//    freopen("group.in","r",stdin);
//    freopen("group.out","w",stdout);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) 
    {
        a[i]=read();
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&i-q[head]>k) head++;//如果隊首的元素已經不在範圍裏,踢出隊列 
        dp[i]=f[q[head]]+sum[i];//取出隊首元素,更新dp[i]的值 
        f[i+1]=dp[i]-sum[i+1];//更新 f[i+1]
        while(head<tail&&f[q[tail-1]]<f[i+1]) tail--;//維護單調隊列,將小於f[i+1]的數都踢出去 
        q[tail++]=i+1;
    }
    printf("%I64d",dp[n]);
    return 0;
}

>>>>總結

通過這道題目,我們還可以知道

對於一類dp,狀態轉移方程抽象為:dp[i]=max(f[j])+g[i] (l[i]<=j<=r[i])

並且l[i]~r[i]單調不減時,我們都可以考慮用單調隊列優化

步驟:踢出過時元素,更新dp值,新元素入隊並且維護單調隊列

那麽就到這裏啦!

完結撒花?(?????)?

題目來源: 2019.2.19楊雅儒學長的考試題

【單調隊列優化dp】 分組