POJ3017 Cut the Sequence

POJ3017 Cut the Sequence

题目大意

给定一个一个长度为 N 的序列 A,要求把该序列划分成若干段,其中每一段中的数的和不大于 M,现在需要使得每一段中数的最大值的和最小,求该最小值。

0n1050m10110ai106

解题思路

可以想到动态规划,设 f(i) 表示考虑前 i 位的划分时的答案。

容易有状态转移方程:

f(i)=min{f(j)+max{aj+1ai}},(sum{aj+1ai}m)

有一个比较显然的性质,f 是单调不降的。

故我们做出以下思考,设 max{aj+1ai}=ak,即最大值下标为 k

那么:

f(i)=min{f(j)+ak}=min{f(j)}+ak

根据上面提到的性质,此时,j 当然越小越好。

总结一下,即,对于 ak=max{aj+1ai}j最小时转移 f(i)=f(j)+ak

形象的说,对于 ak 我们取其 管辖区间 (即使得其为最大值的区间)的左端点转移。

我们考虑如何求出这些区间。

我们从前往后依次遍历原序列,同时维护一个下降的单调队列,对于其中的 qi 其的 管辖区间 即为 [qi1+1,i],其余的点无法成为最大值,不需要转移。

对于队头元素,其 管辖区间 的左端点即满足转移方程中的条件 sum{aj+1ai}m,这个用前缀和加一个指针就可以做到(利用前缀和的单调性)。

我们得知了队列中的每一个点的 管辖区间 的左端点,用一个 multiset 动态维护(在加入和弹出队列时操作 multiset)。

值得注意的是,队首元素的左端点随 i 变化,不使用 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;
}
posted @   DeepSeaSpray  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示