zoukankan      html  css  js  c++  java
  • Vijos 1617 超级教主(单调队列优化dp)

    题目

    Description

    LHX教主很能跳,因为Orz他的人太多了。教主跳需要消耗能量,每跳1米就会消耗1点能量,如果教主有很多能量就能跳很高。教主为了收集能量,来到了一个神秘的地方,这个地方凡人是进不来的。在这里,教主的正上方每100米处就有一个能量球(也就是这些能量球位于海拔100,200,300……米处),每个能量球所能提供的能量是不同的,一共有N个能量球(也就是最后一个能量球在N imes 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
    ",dp[n]);
    }
  • 相关阅读:
    bzoj2115: [Wc2011] Xor
    bzoj2844: albus就是要第一个出场
    hdu3949
    bzoj2487: Super Poker II
    bzoj3456: 城市规划
    bzoj3992: [SDOI2015]序列统计
    ubuntu 使用命令行登录oracle
    ubuntu安装docker
    linux查询硬件信息
    ubuntu oracle 环境搭建
  • 原文地址:https://www.cnblogs.com/wzx-RS-STHN/p/13410996.html
Copyright © 2011-2022 走看看