【單調隊列優化dp】 分組
【單調隊列優化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】 分組