【POJ3017】Cut the Sequence

题目大意:给定一个长度为 N 的序列,将序列划分成若干段,保证每段之和不超过 M,问所有段的最大值之和最小是多少。

题解:设 \(f[i]\) 表示前 i 个数满足上述条件的最优解,显然有状态转移方程\[f[i]=min\{f[j]+max_{j+1\le 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\n",f[n]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wzj-xhjbk/p/10474463.html