思路:dp[i]表示前第i项的最小值。
很容易写出状态转移方程:dp[i]=min(dp[j]+max(a[j+1],a[i])),j<=i;
优化的思想是:我们需要快速的在前面找到一个j,同时还要找出j+1到i之间的最大项是什么,这样就能快速实现,最直接的思路是求RMQ,但是这题可以用单调队列。
用单调队列维护一个递减的序列,对于当前的a[i],先将队尾小于或等于a[i]的元素删掉,再把a[i]插入队尾。比如拿sample中的标号4-8一段8 1 8 2 1来看,那么队列里面存的标号就是6 7 8,对应的a[]的值就是8 2 1。
其实我们可以发现,在第6项以后的最值就是队列里存的编号7,换句话说,我们枚举一个j,那么下一个最值就是j+1对应的。
单调队列中的下一个元素就是后一段的最大值。
最后还要保证任何区间的和不超过m,这个只要保证队列的第一项到最后一项之和都不超过m,那么枚举的任何子区间都不会超过m。
参考博客:https://www.cnblogs.com/staginner/archive/2012/04/02/2429850.html
https://www.2cto.com/kf/201410/346223.html
代码:
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<list>
#include<queue>
#include <deque>
const int maxn=1e5+9;
typedef long long ll;
ll n,m;
ll dp[maxn];
ll a[maxn];
struct node
{
int index;
ll val;
}que[maxn];
ll min(ll i,ll j)
{
return i<j?i:j;
}
int main(int argc, char const *argv[])
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
while(scanf("%lld%lld",&n,&m)!=EOF)
{
//sum[0]=0;
bool f=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
if(a[i]>m) f=1;
//sum[i]=sum[i-1]+a[i];
}
if(f==1)
{
printf("-1\n");
continue;
}
int head,tail,pos;
head=tail=0,pos=1;
ll s=a[1];
que[tail].val=dp[1]=a[1];
que[tail++].index=1;
for(int i=2;i<=n;i++)
{
s+=a[i];
while(s>m&&pos<i)
{
s-=a[pos];
pos++;
}
while(head<tail&&que[tail-1].val<=a[i])
{
tail--;
}
que[tail].val=a[i],que[tail++].index=i;
while(que[head].index<pos&&head<tail)
{
head++;
}
dp[i]=dp[pos-1]+que[head].val;
for(int j=head;j<tail-1;j++)
{
dp[i]=min(dp[i],dp[que[j].index]+que[j+1].val);
}
}
printf("%lld\n",dp[n]);
}
return 0;
}