Vijos 1617 超級教主(單調佇列優化dp)
題目
Description
LHX教主很能跳,因為Orz他的人太多了。教主跳需要消耗能量,每跳1米就會消耗1點能量,如果教主有很多能量就能跳很高。教主為了收集能量,來到了一個神祕的地方,這個地方凡人是進不來的。在這裡,教主的正上方每100米處就有一個能量球(也就是這些能量球位於海拔100,200,300……米處),每個能量球所能提供的能量是不同的,一共有N個能量球(也就是最後一個能量球在N \times 100N×100米處)。教主為了想收集能量,想跳著吃完所有的能量球。教主可以自由控制他每次跳的高度,接著他跳起把這個高度以下的能量球都吃了,他便能獲得能量球內的能量,接著吃到的能量球消失。教主不會輕功,教主不會二段跳,所以教主不能因新吃到的能量而變化此次跳躍的高度。並且教主還是生活在地球上的,所以教主每次跳完都會掉下來。問教主若要吃完所有的能量球,最多還能保留多少能量。
Input
第1行包含兩個正整數N,M,表示了能量球的個數和LHX教主的初始能量。
第2行包含N個非負整數,從左到右第I個數字依次從下向上描述了位於I×100I×100米位置能量球包含的能量,整數之間用空格隔開。
N≤2000000N≤2000000。
保證對於所有資料,教主都能吃到所有的能量球,並且能量球包含的能量之和不超過2^{31}-1231−1
Output
僅包括一個非負整數,為教主吃完所有能量球后最多保留的能量。
Sample Input
3 200 200 200 200
Sample Output
400
思路
首先這是很明顯的一道dp題;
樣例解釋:
第1次跳100米,得到200能量,消耗100能量,所以落地後擁有300能量。
第2次跳300米,吃到剩下的第3棵能量球,消耗擁有的300能量,得到400能量。
若第1次跳200米,第2次跳300米,最後剩餘300能量。
所以我們設dp[i]表示鴨完前i 個球,保留的最多能量;
那麼很顯然 dp[i]=max(dp[i], dp[j]- i*100+sum[i]-sum[j]);(j<i)
sum[]是字首和,sum[i]-sum[j] 表示 j+1 - i 的和;
那麼dp[i]=max(dp[i], dp[j]-sum[j]+(sum[i]- i*100));
將式子轉化後,我們發現sum[i]- i*100 是一個定值;
那麼dp[i] 要儘可能大 ,dp[j]-sum[j] 也要儘可能大;
所以 方程就有關 dp[j]-sum[j] 的大小;
那麼就可以設一個嚴格單調下降的佇列 q[] ;
存每個 dp[j]-sum[j]的位置 j ; 那麼 head 的位置就是最大的 dp[j]-sum[j];
那麼什麼時候 踢隊頭 呢?
觀察 轉移方程 從dp[j] 跳上去,需要 花費 i*100 能量;
所有在此過程中,dp[j]>=i*100 必須滿足;
所以 如果 dp[j]< i*100 就要踢隊頭;
程式碼
#include<bits/stdc++.h>//可愛的萬能頭 ^_^ typedef long long ll; using namespace std; inline ll read() { ll a=0,f=1; char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1; c=getchar();} while (c>='0'&&c<='9') {a=a*10+c-'0'; c=getchar();} return a*f; }//快讀 ll n,m; ll q[2000010]; ll dp[2000010],sum[2000010],a[2000010]; int main() { n=read();m=read(); for(ll i=1;i<=n;i++) { a[i]=read(); sum[i]=sum[i-1]+a[i];//統計字首和 } dp[0]=m;//如果不跳,那麼能量就是自身原來保留的能量 ll head=1,tail=1;//tail=1,代表入隊了0,因為是字首和所以需要入隊0 for(ll i=1;i<=n;i++) { while(head<=tail&&dp[q[head]]<i*100)//根據”思路“踢隊頭 head++; dp[i]=dp[q[head]]-sum[q[head]]+sum[i]-i*100;//轉移方程 while(head<=tail&&dp[i]-sum[i]>dp[q[tail]]-sum[q[tail]])//嚴格單調下降 tail--; tail++;q[tail]=i;//隊尾入隊 } printf("%lld\n",dp[n]); }