【题解】裁剪序列
#【题解】【单调队列优化dp】裁剪序列
##题目传送门
##分析
首先考虑朴素的做法,设$f_i$表示把前i个数分成若干段,在满足每段中所有的数的和不超过M的前提下,各段的最大值之和最小是多少。容易写出状态转移方程:
\(f_i=\min\{f_j+\max\limits_{j+1 \leq k\leq i} \{a_k\}\},(0\leq j<i且\sum_{p=j+1}^i a_p<=M)\)
但这样转移是$O(N^2)$的,我们再思考有哪些决策j是我们“真正需要”的(可以成为最优决策)
观察我们的状态转移方程,发现决策的两个部分:
1.$f_j$是关于j的单调不下降函数
2.$\max\limits_{j+1 \leq k\leq i} {a_k}$是关于j的单调不上升函数
所以,如果$\max\limits_{j+1 \leq k\leq i} {a_k}=\max\limits_{j \leq k\leq i} {a_k}\(并且\)\sum_^i a_p<=M$
决策j-1一定比决策j更优
那假设我们记录一个maxn表示j到i的最大值,我们从大到小枚举j,每次更新maxn,根据上面的结论,只有使maxn变大的j才有可能作为最优决策。特殊地,若j为最小的满足$\sum_^i a_p<=M$的j,其也有可能更新答案
如下图,j=3使得maxn变大,因此j=3可以作为最优决策。j=1是最小的满足$\sum_^i a_p<=M$的j,所以j=1也可以作为最优决策

于是我们思考如何维护这个决策集合,即i增加到i+1时,决策集合发生了哪些变化。假设原先的决策集合为$S={j_1,j_2,\cdots,j_s}$,不妨令$j_1<j_2<\cdots<j_s$,显然$a[j_1]>a[j_2]>\cdots>a[j_s]$
那么当i变化后,我们模拟一下S的更新,先将$i-1$与$j_s$进行比较,若$a[i-1]>=a[j_s]$,把$j_s$扔出S,
然后,按照相同方式依次比较$i-1$与$j_,j_,j_\cdots,$直到集合为空或$a[i-1]<a[j_]$,然后将i-1加入集合
除此之外我们还要从$j_1$开始逐个检验$\sum_^i a_p$是否小于等于$M$,若否,则将其出队
是的,我已经情不自禁开始说出队了,因为这实际上就是一个单调队列呀
但是题目到这还没有做完,因为单调队列存的每个元素都是可能的最优决策,若不加以优化,仍要遍历整个队列导致复杂度爆炸
我们再来回顾下,我们原始的方程式:
\(f_i=\min\{f_j+\max\limits_{j+1 \leq k\leq i} \{a_k\}\},(0\leq j<i且\sum_{p=j+1}^i a_p<=M)\)
这个$\max\limits_{j+1 \leq k\leq i} {a_k}$好像很难处理,我们先想个简单的,如果min里面只有$f_j$的话怎么做?
既然是求最值,可以用一个二叉堆来维护,让它和单调队列建立一个映射,两个数据结构同时插入同时删除,至于处理$\max\limits_{j+1 \leq k\leq i} {a_k}$,就轮到单调队列发挥作用了
在单调队列中,我要从尾部插入一个j,那我删除的那些节点的max就不用管了,而除了队尾,队列中的其他元素的max值也不会变(想一想,为什么),因此我只需在堆中更新队尾的max值
于是这道题就做完了
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
inline int read()
{
register int x=0,w=1;
register char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if(ch=='-'){ch=getchar();w=-1;}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return x*w;
}
struct node{
int id,val,y;
};//用结构体方便“懒惰删除”
bool operator<(const node &x,const node &y){
return x.val>y.val;
}
int q1[100005],l=1,r;
priority_queue<node>q2;
int n,m,st[100005][20],c[100005],a[100005],sum[100005];
int f[100005],used[100005],maxlog[100005];
int maxn(int l,int r)
{
int p=maxlog[r-l+1];
return max(st[l][p],st[r-(1<<p)+1][p]);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;++i)
{
a[i]=read();
if(a[i]>m){
cout<<-1<<endl;
return 0;
}
sum[i]=sum[i-1]+a[i];
c[i]=lower_bound(sum,sum+i+1,sum[i]-m)-sum+1;
// if(c[i]==1) c[i]=0;
st[i][0]=a[i];
}
for(int len=1;len<=n;++len)
{
maxlog[len]=maxlog[len-1];
if(1<<(maxlog[len]+1)<=len) maxlog[len]++;
}
for(int len=1;len<=maxlog[n];++len)
{
for(int i=1;i+(1<<len)-1<=n;++i)
{
st[i][len]=max(st[i][len-1],st[i+(1<<(len-1))][len-1]);
}
}
// for(int i=1;i<=n;++i)
// cout<<sum[i]<<" ";
//puts("");
// for(int i=1;i<=n;++i)
// cout<<sum[i]-m<<" ";
//puts("");
// for(int i=1;i<=n;++i)
// cout<<c[i]<<" ";
memset(f,0x3f,sizeof f);
f[0]=0;
for(int i=1;i<=n;++i)
{
f[i]=min(f[i],f[c[i]-1]+maxn(c[i],i));
while(l<=r&&c[i]>q1[l]) l++,used[q1[l-1]]=1;
while(l<=r&&a[q1[r]]<=a[i]) r--,used[q1[r+1]]=1;
q1[++r]=i;
if(l<r)
q2.push((node){q1[r-1],f[q1[r-1]]+a[i],a[i]});
while(!q2.empty()&&(used[q2.top().id]||(q2.top().y<maxn(q2.top().id+1,i))))
q2.pop();
if(!q2.empty())
f[i]=min(f[i],q2.top().val);
}
// for(int i=1;i<=n;++i)
// cout<<f[i]<<" ";
//
cout<<f[n];
return 0;
}

浙公网安备 33010602011771号