POJ3017Cut the Sequence
Posted wzj-xhjbk
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POJ3017Cut the Sequence相关的知识,希望对你有一定的参考价值。
题目大意:给定一个长度为 N 的序列,将序列划分成若干段,保证每段之和不超过 M,问所有段的最大值之和最小是多少。
题解:设 (f[i]) 表示前 i 个数满足上述条件的最优解,显然有状态转移方程[f[i]=min{f[j]+max_{j+1le k le i}{a[k]}}],发现若能够在 (O(1)) 的时间内求得静态区间最小的 a 值,则时间复杂度为 (O(n^2))。
可以发现,这个算法复杂度的瓶颈是每次都需要枚举 j 来做状态转移,于是观察递推式的结构,由于 f[i] 表示的是每段的最大值之和,可知 f[i] 这个序列单调不减,同时,对于一段区间的最大值而言,可以有很多转移的方式,既然 f[i] 序列单调不减,则可以将决策直接定在能够符合条件的 j 的最小值即可。通过这样,将枚举 j 寻找决策点的情况转化成了对于每个区间最大值对应的区间的最前端进行决策。而每个区间最大值可以采用单调队列进行维护即可,而取哪个点进行转移的最终决策还是要通过比较大小才能够知道,在这里可以用平衡树进行维护。
(QAQ看了好长时间才理解
代码如下
#include <cstdio>
#include <set>
using namespace std;
const int maxn=1e5+10;
int n,a[maxn],q[maxn];
long long m,sum[maxn],f[maxn];
multiset<long long> s;
int main(){
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]>m){puts("-1");return 0;}
sum[i]=sum[i-1]+a[i];
}
int l=1,r=0,j=0;
for(int i=1;i<=n;i++){
while(sum[i]-sum[j]>m)++j;
while(l<=r&&q[l]<=j){
if(l<r)s.erase(a[q[l+1]]+f[q[l]]);
++l;
}
while(l<=r&&a[q[r]]<=a[i]){
if(l<r)s.erase(a[q[r]]+f[q[r-1]]);
--r;
}
q[++r]=i;
if(l<r)s.insert(a[i]+f[q[r-1]]);
f[i]=f[j]+a[q[l]];
if(l<r)f[i]=min(f[i],*s.begin());
}
printf("%lld
",f[n]);
return 0;
}
以上是关于POJ3017Cut the Sequence的主要内容,如果未能解决你的问题,请参考以下文章
$Poj3017 Cut The Sequence$ 单调队列优化$DP$