题意
给定一个长度为
n
n
的序列,把序列分成若干段。每段的和不能超过 。定义每段的价值为这段的最大值,求所有段价值总和最小是多少。
1≤n≤105
1
≤
n
≤
10
5
思路
做这种单调队列优化
dp
d
p
的题目,首先还是得列出暴力的
dp
d
p
方程,设
dpi
d
p
i
为
[1,i]
[
1
,
i
]
的最小价值,不难得到:
dpi=min{dpj−1+max{aj...ai}}∑k=jiak≤M
d
p
i
=
m
i
n
{
d
p
j
−
1
+
m
a
x
{
a
j
.
.
.
a
i
}
}
∑
k
=
j
i
a
k
≤
M
然后尝试用单调队列优化。首先
j
j
的最小值通过尺取确定。其次,发现如果按顺序扫的时候出现了一个较大的 ,那么在
ai
a
i
前比它小的数下标对应的
dp
d
p
值都用不到了,应为完全可以通过拉大上一个区间,取到最后一个比
ai
a
i
大的数上。所以我们只用维护一个关于
a
a
<script type="math/tex" id="MathJax-Element-107">a</script> 的递减的单调队列,每次转移只从单调队列里转移即可。要注意特判单调队列里的队首元素。
代码
#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;
}