【题解】裁剪序列
【题解】【单调队列优化dp】裁剪序列
题目传送门
分析
首先考虑朴素的做法,设表示把前i个数分成若干段,在满足每段中所有的数的和不超过M的前提下,各段的最大值之和最小是多少。容易写出状态转移方程:
但这样转移是的,我们再思考有哪些决策j是我们“真正需要”的(可以成为最优决策)
观察我们的状态转移方程,发现决策的两个部分:
1.是关于j的单调不下降函数
2.是关于j的单调不上升函数
所以,如果并且
决策j-1一定比决策j更优
那假设我们记录一个maxn表示j到i的最大值,我们从大到小枚举j,每次更新maxn,根据上面的结论,只有使maxn变大的j才有可能作为最优决策。特殊地,若j为最小的满足的j,其也有可能更新答案
如下图,j=3使得maxn变大,因此j=3可以作为最优决策。j=1是最小的满足的j,所以j=1也可以作为最优决策
于是我们思考如何维护这个决策集合,即i增加到i+1时,决策集合发生了哪些变化。假设原先的决策集合为,不妨令,显然
那么当i变化后,我们模拟一下S的更新,先将与进行比较,若,把扔出S,
然后,按照相同方式依次比较与直到集合为空或,然后将i-1加入集合
除此之外我们还要从开始逐个检验是否小于等于,若否,则将其出队
是的,我已经情不自禁开始说出队了,因为这实际上就是一个单调队列呀
但是题目到这还没有做完,因为单调队列存的每个元素都是可能的最优决策,若不加以优化,仍要遍历整个队列导致复杂度爆炸
我们再来回顾下,我们原始的方程式:
这个好像很难处理,我们先想个简单的,如果min里面只有的话怎么做?
既然是求最值,可以用一个二叉堆来维护,让它和单调队列建立一个映射,两个数据结构同时插入同时删除,至于处理,就轮到单调队列发挥作用了
在单调队列中,我要从尾部插入一个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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· 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工具