【题解】裁剪序列

【题解】【单调队列优化dp】裁剪序列

题目传送门

分析

首先考虑朴素的做法,设fi表示把前i个数分成若干段,在满足每段中所有的数的和不超过M的前提下,各段的最大值之和最小是多少。容易写出状态转移方程:

fi=min{fj+maxj+1ki{ak}},(0j<ip=j+1iap<=M)

但这样转移是O(N2)的,我们再思考有哪些决策j是我们“真正需要”的(可以成为最优决策)

观察我们的状态转移方程,发现决策的两个部分:
1.fj是关于j的单调不下降函数
2.maxj+1ki{ak}是关于j的单调不上升函数

所以,如果maxj+1ki{ak}=maxjki{ak}并且p=jiap<=M

决策j-1一定比决策j更优

那假设我们记录一个maxn表示j到i的最大值,我们从大到小枚举j,每次更新maxn,根据上面的结论,只有使maxn变大的j才有可能作为最优决策。特殊地,若j为最小的满足p=j+1iap<=M的j,其也有可能更新答案

如下图,j=3使得maxn变大,因此j=3可以作为最优决策。j=1是最小的满足p=j+1iap<=M的j,所以j=1也可以作为最优决策

于是我们思考如何维护这个决策集合,即i增加到i+1时,决策集合发生了哪些变化。假设原先的决策集合为S={j1,j2,,js},不妨令j1<j2<<js,显然a[j1]>a[j2]>>a[js]

那么当i变化后,我们模拟一下S的更新,先将i1js进行比较,若a[i1]>=a[js],把js扔出S,
然后,按照相同方式依次比较i1js1,js2,js3,直到集合为空或a[i1]<a[jsx],然后将i-1加入集合

除此之外我们还要从j1开始逐个检验p=jx+1iap是否小于等于M,若否,则将其出队

是的,我已经情不自禁开始说出队了,因为这实际上就是一个单调队列呀

但是题目到这还没有做完,因为单调队列存的每个元素都是可能的最优决策,若不加以优化,仍要遍历整个队列导致复杂度爆炸

我们再来回顾下,我们原始的方程式:

fi=min{fj+maxj+1ki{ak}},(0j<ip=j+1iap<=M)

这个maxj+1ki{ak}好像很难处理,我们先想个简单的,如果min里面只有fj的话怎么做?

既然是求最值,可以用一个二叉堆来维护,让它和单调队列建立一个映射,两个数据结构同时插入同时删除,至于处理maxj+1ki{ak},就轮到单调队列发挥作用了

在单调队列中,我要从尾部插入一个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;
}
posted @   glq_C  阅读(167)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示