动态规划练习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; }