动态规划练习5

复仇滚木

题目描述:

      lw的狂暴药水经常到处乱洒!有一棵树因为被lw狂暴过很多次了,它变异了!它给自己取名为复仇滚木,它要找lw报仇(什么仇什么怨)。lw听到了这个消息,立马着手准备去教训它。他想从他的n把斧头中选取一些连续的斧头,但最多选m把。每把斧头都有一个威力值,但因为有些斧头很久没磨了,所以可能有些斧头威力值为负。lw想知道他能获得的威力值最大为多少。

输入格式:

第一行两个数,分别为n和m,接下来n行,每行一个数,即这个斧头的威力值。

输出格式:

      一个数,即最大威力值。

 

样例输入:log.in

4 2

8

-2

3

7

样例输出:log.out

10(选3和7)

数据范围:

对于30%的数据,n,m<=1000

对于100%的数据,n,m<=100000

题解:

大水题。。。

s[i]表示前缀和,f[i]表示i为区间右端点时的可以取得的最大值。

f[i]=s[i]-min{s[k]|i-m≤k≤i}

i之前m个s[]组成一个队列。在这m个s[]中,如果i<j,且s[i]>s[j]那么s[i]肯定用不到,因为s[i]比s[j]先出队,s[i]又比s[j]大所以肯定用不到它。所以可以把该队列的元素满足:S1<S2<S3<……<Sk(因为不一定有m个元素,所以用k表示)

这样的话取最小值也是O(1)的,每进一个元素从右边开始比较,如果比它大就删掉(满足刚才的性质),当然加了元素之后还要判断是否队列超过m个元素,超过就把左边的删除。摊还分析一下,每个元素进队一次,出队一次,所以总复杂度是O(n)的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#define inf (2000000000)
using namespace std;
typedef long long lol;
lol q[100001],head,tail=1,n,m,a[100001],ans=-inf;
lol gi()
{
    lol ans=0,f=1;
    char i=getchar();
    while(i<'0'||i>'9'){if(i=='-')f=-1;i=getchar();}
    while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
    return ans*f;
}
int main()
{
    freopen("log.in","r",stdin);
    freopen("log.out","w",stdout);
    lol i,j;
    n=gi();m=gi();
    for(i=1;i<=n;i++)
    {
        a[i]=gi();
        a[i]+=a[i-1];
    }
    for(i=1;i<=n;i++)
    {
        while(i-q[head]>m)head++;
        ans=max(ans,a[i]-a[q[head]]);
        while(tail-1>=head&&a[q[tail-1]]>a[i])tail--;
        q[tail++]=i;
    }
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/huangdalaofighting/p/7244306.html