1. 程式人生 > 實用技巧 >Vijos 1617 超級教主(單調佇列優化dp)

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≤2000000N2000000。
保證對於所有資料,教主都能吃到所有的能量球,並且能量球包含的能量之和不超過2^{31}-12311

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]);
}