题意
给定一个长度为
的序列,把序列分成若干段。每段的和不能超过
。定义每段的价值为这段的最大值,求所有段价值总和最小是多少。
思路
做这种单调队列优化
的题目,首先还是得列出暴力的
方程,设
为
的最小价值,不难得到:
然后尝试用单调队列优化。首先
的最小值通过尺取确定。其次,发现如果按顺序扫的时候出现了一个较大的
,那么在
前比它小的数下标对应的
值都用不到了,应为完全可以通过拉大上一个区间,取到最后一个比
大的数上。所以我们只用维护一个关于
的递减的单调队列,每次转移只从单调队列里转移即可。要注意特判单调队列里的队首元素。
代码
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 100003
typedef long long LL;
using namespace std;
int a[N];LL dp[N];
bool cmp(int x,int y){return x>y;}
template<int *a>struct monoque
{
int q[N],L,R;
monoque(){L=1,R=0;}
void clear(){L=1,R=0;}
void del(int _){while(L<=R&&!cmp(a[q[R]],a[_]))R--;}
void push(int _){q[++R]=_;}
int front(){return q[L];}
int rear(){return q[R];}
void pop(int _){while(L<=R&&_>=q[L])L++;}
};
monoque<a>mq;
int main()
{
int n;LL m;
scanf("%d%lld",&n,&m);
FOR(i,1,n)
{
scanf("%d",&a[i]);
if(a[i]>m)
{
printf("-1\n");
return 0;
}
}
LL sum=0;
int L=1;
FOR(R,1,n)
{
sum+=a[R];
mq.del(R);
mq.push(R);
while(L<=R&&sum>m)sum-=a[L++];
mq.pop(L-1);
dp[R]=dp[L-1]+a[mq.front()];
FOR(i,mq.L+1,mq.R)dp[R]=min(dp[R],dp[mq.q[i-1]]+a[mq.q[i]]);
}
printf("%lld\n",dp[n]);
return 0;
}