POJ3017 Cut the Sequence
POJ3017 Cut the Sequence
题目大意
给定一个一个长度为 的序列 ,要求把该序列划分成若干段,其中每一段中的数的和不大于 ,现在需要使得每一段中数的最大值的和最小,求该最小值。
解题思路
可以想到动态规划,设 表示考虑前 位的划分时的答案。
容易有状态转移方程:
有一个比较显然的性质, 是单调不降的。
故我们做出以下思考,设 ,即最大值下标为 。
那么:
根据上面提到的性质,此时, 当然越小越好。
总结一下,即,对于 ,最小时转移 。
形象的说,对于 我们取其 管辖区间 (即使得其为最大值的区间)的左端点转移。
我们考虑如何求出这些区间。
我们从前往后依次遍历原序列,同时维护一个下降的单调队列,对于其中的 其的 管辖区间 即为 ,其余的点无法成为最大值,不需要转移。
对于队头元素,其 管辖区间 的左端点即满足转移方程中的条件 ,这个用前缀和加一个指针就可以做到(利用前缀和的单调性)。
我们得知了队列中的每一个点的 管辖区间 的左端点,用一个 multiset
动态维护(在加入和弹出队列时操作 multiset
)。
值得注意的是,队首元素的左端点随 变化,不使用 multiset
维护,单独转移。
参考代码
//#include<bits/stdc++.h> #include<set> #include<cstdio> using namespace std; #define int long long const int maxn=1e5; int n,m; int a[maxn+5]; int s[maxn+5]; int f[maxn+5]; int q[maxn+5],l,r; multiset<int> c; signed main(){ int k; scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s[i]=s[i-1]+a[i]; k=0,l=1,r=0; for(int i=1;i<=n;i++){ if(a[i]>m){puts("-1");return 0;} while(s[i]-s[k]>m) k++; while(l<=r&&a[q[r]]<=a[i]){ if(l<r) c.erase(c.find(f[q[r-1]]+a[q[r]])); r--; } q[++r]=i; if(l<r) c.insert(f[q[r-1]]+a[q[r]]); while(l<=r&&q[l]<=k){ if(l<r) c.erase(c.find(f[q[l]]+a[q[l+1]])); l++; } f[i]=f[k]+a[q[l]]; if(l<r) f[i]=min(f[i],*c.begin()); } printf("%lld",f[n]); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】