Cut the Sequence

Cut the Sequence

有一个长度为n的序列\(\{a_i\}\),现在求将其划分成若干个区间,并保证每个区间的和不超过m的情况下,每个区间的最大值的和的最小值,\(0 < N ≤ 100 000\)

不难想到,设\(f_i\)表示把前i个位置划分后的所求,设s为a的前缀和,于是有

\[f_i=\min_{j=0,s_i-s_j\leq m}^{i-1}\{f_j+\max_{k=j+1}^i\{a_k\}\}\]

关键在于这是\(O(n^2)\)递推,于是有以下结论优化

  • f具有单调性

把得到\(f_i\)的方案中,去除掉第i个位置变为\(f_{i-1}\),要么不变,要么变小,而此时得到\(f_{i-1}\leq f_i\),于是得证。

  • 最优决策点j必然满足其中之一
  1. \(\max_{k=j}^i\{a_k\}=a_j\)(即\(a_j\)大于等于\(a_{j+1},...,a_i\)
  2. \(s_i-s_{j-1}>m\)(即j是最小的j使\(s_i-s_j\leq m\))

对于最优决策点而言,如果前面的决策点比其更优,必然有\(s_i-s_{j-1}\leq m\),并且\(f_{j-1}+\max_{k=j}^i\{a_k\}\leq f_j+\max_{k=j+1}^i\{a_k\}\),而\(f\)本身具有单调性,为了让结果满足条件\(\max_{k=j}^i\{a_k\}= \max_{k=j+1}^i\{a_k\}\),即\(a_k\leq \max_{k=j+1}^i\{a_k\}\)

于是当\(s_i-s_{j-1}> m\),\(a_k\geq \max_{k=j+1}^i\{a_k\}\)时,j可能为最优决策点。

因此对于即j是最小的j使\(s_i-s_j\leq m\),我们假设在第i-1个位置的j已经维护出来,而考虑第i个位置,此时多加了一个\(a_i\),如果不能满足条件,只要往后拉即可,这样可以做到\(O(n)\)

至于\(a_j\)大于等于\(a_{j+1},...,a_i\),故如果转移到求\(f_{i+1}\),多了一个\(a_{i+1}\),如果\(a_{i+1}>a_j\),必然它就不再满足了,于是可以去除,而\(s_{i}-s_{j}\)又是一个单调的函数,于是我们可以维护单调递减的单调队列维护决策点a的单调性,并保证s的不超过范围。

现在问题在于如何快速求出决策点转移过来的值,易知对于决策点j\(f_j\)已是确定了,因为单调队列的性质,容易知道,决策点j的\(\max_{k=j+1}^i\{a_k\}\)也就是j在单调队列中的位置的下一个位置的a,根据这一性质,注意对单调队列末尾的特判即可。

参考代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#define il inline
#define ri register
#define ll long long
#define Size 100100
using namespace std;
multiset<int>S;
ll dp[Size];
int a[Size],T[Size],L,R;
template<class free>
il void read(free&);
template<class free>
il free Min(free,free);
int main(){
    int n;ll m,sum(0);read(n),read(m);
    for(int i(1);i<=n;++i)
        read(a[i]),dp[i]=1e11;dp[0]=0,L=1,R=0;
    for(int i(1),p(0);i<=n;++i){sum+=a[i];
        while(sum>m)sum-=a[p+1],++p;if(p==i)return puts("-1"),0;T[R+1]=i;
        while(L<=R&&T[L]<p)S.erase(S.find(dp[T[L]]+a[T[L+1]])),++L;
        while(L<=R&&a[T[R]]<a[i])S.erase(S.find(dp[T[R]]+a[T[R+1]])),--R;
        if(L<=R)S.erase(S.find(dp[T[R]]+a[T[R+1]])),S.insert(dp[T[R]]+a[i]),
                    dp[i]=*S.begin();T[++R]=i;
        if(dp[p]+a[T[L]]<dp[i])dp[i]=dp[p]+a[T[L]];S.insert(dp[i]+a[i+1]);
    }printf("%lld",dp[n]);
    return 0;
}
template<class free>
il free Min(free a,free b){
    return a<b?a:b;
}
template<class free>
il void read(free &x){
    x&=0;ri char c;while(c=getchar(),c<'0'||c>'9');
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}

转载于:https://www.cnblogs.com/a1b3c7d9/p/10962007.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值