首先,我们可以先想一想朴素算法,推出DP,i表示分了几段,则可以推出$$F[i]=min_{1<=j<=i}(f[j]+max_{j+1<=k<=i}(a[k]))$$
点击查看代码
memset(f,0x3f,sizeof f);f[0]=0;for(int i=1;i<=n;i++){for(int j=0;j<i;j++){int tmp=0;ll sum=0;for(int k=j+1;k<=i;k++){tmp=max<ll>(tmp,a[k]);sum+=a[k];}if(sum<=m)f[i]=min(f[i],f[j]+tmp);}}cout<<f[n];
这是\(O(n^2)\)的算法复杂度,如何优化,我们可以想到用双端队列维护\(a\)数组,以单调递减趋势
用一个变量\(pos\)控制他的左边界别超出\(m\),如何维护最优值呢?
显然\(F[i]\)成一个单调递增趋势,一个区间\(a\)的值越大,他包含的范围应该越大,当max(a[j+1—>i])的值固定,那么j越小越好,当不固定时,可以排除一些情况,即$$j1<j2 a[j1]<=a[j2]是无用的$$
这是我们可以看出用单调队列维护由于f[j]+max(a[j+1—>i])不单调,要用multiset维护一下
与单调队列同步,具体详细看代码
\(F[i]=f[pos-1]+a[q.front()]\)
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define mk make_pair
#define pb push_back
#define lid (rt<<1)
#define rid (rt<<1|1)
#define speed() ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
using namespace std;
const int N = 1e5+5;
ll n,m,a[N],f[N];
multiset <ll> s;
deque <ll> q;
int main()
{speed();cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];if(a[i]>m)return cout<<-1,0;}deque <int> q;int ans=0,pos=1;for(int i=1;i<=n;i++){ans+=a[i];while(ans>m)ans-=a[pos++]; //边界while(q.size()&&q.front()<pos){ll tp=f[q.front()];q.pop_front();//因为单调队列单调递减的缘故,所以front后一个就是maxif(q.size())s.erase(tp+a[q.front()]);//同步}while(q.size()&&a[q.back()]<=a[i]){ll tp=a[q.back()];q.pop_back();if(q.size())s.erase(tp+f[q.back()]);}if(q.size())s.insert(a[i]+f[q.back()]);//队列中每两个相邻元素就得insert一次q.push_back(i);f[i]=f[pos-1]+a[q.front()];//一种可能情况,选择pos->i整个区间,因为该段区间最大值(a[q.front()])与f[k-1]也构成一对可能解if(s.size())f[i]=min<ll>(f[i],*s.begin());}cout<<f[n];return 0;}