poj 3017 Cut the Sequence(单调队列优化DP)

时间:2023-01-18 21:14:54

Cut the Sequence

poj 3017 Cut the Sequence(单调队列优化DP)



\(solution:\)

这道题出的真的很好,奈何数据水啊!

这道题当时看得一脸懵逼,说二分也不像二分,说贪心也不像贪心,说搜索吧这题数据范围怎么这么大?而且这题看起来也实在不好DP,当时是真的满头雾水。只能说是各个都尝试一下。最后还是选了DP来做第一步突破,因为这道题可以用最优子结构来推出最优答案,也符合常规DP套路即设 \(F[i][j]\) 表示将前面 $ i $ 个数分成 $ j $ 份,但是这一道题没有说具体的份数,而且数据范围很大,所以我们直接设 \(F[i]\) 表示前 $ i $ 个数的最优答案。这个的转移方程还是比较好想的:

\(F[i]=^{\quad\quad min}_{0<j<i,\sum^{k<i}_{k=j+1}A_k\leq M}\{F[j]+max\{A_k \} \}\)

这样我们发现我们得到了一种 \(n^2\) 的做法,然后数据范围告诉我们这样还不够。我们还得优化,但是 \(Van\) 是真的不会优化了,这个式子很有单调队列的样子,但是这个 \(max\{A_k \}\) ,这个式子似乎无懈可击啊!(于是他看了看书,表示:考这道题我直接爆零离场算了)

我们知道一个优秀的搜索或DP一定要尽可能去避免一些没有用的状态和决策,书上说:要保持决策集合的高度有效性和秩序性。所以我们看一看我们会不会枚举了一些没有什么用的 \(F[j]\) 。我们可以分析得出只有两种 $F[j] $ 对我们有用:

  1. $ j $ 是满足\(\sum^{k<i}_{k=j+1}A_k\leq M\) 的最小的 $ j $
  2. 我们的 \(A_j\) 是从 $ j $ 到 $ i $ 中间最大的权值

这个其实我们静下心来想想就可以明白,如果一个数不是从它开始到第 $ i $ 个数中最大的而且它不是可枚举的最小的第 $ j $ 个数,那么它一定不是最优解,它的左边那一个 \(F[j-1]\) 一定更优,因为它比 $F[j ] $ 小,而且最大值一样。于是我们用维护一个包含所有的高效决策的单调队列,显然 $ j $ 单调递增 $A_j $ 单调递减。然后我们要支持及时删去对头失效的决策,然后更新队尾加元素。

然后是这一题非常神奇的一个地方:我们要得到答案还需要 \(max\{A_k \}\),这个我们可以倍增 \(ST\) 表,但是我们分析题目性质就能发现一个跟奇妙的地方,我们的单调队列里的下一个元素就是我们要求得 \(max\{A_k \}\) ,这个只要想一想都知道以为我们维护的是(所有的高效决策的单调队列,并且显 $ j $ 单调递增 $A_j $ 单调递减)。

然后还有一个我想吐槽的地方(奈何这一题数据水),我们的单调队列的对头不一定就是我们的最佳决策,这也是说我们队列里 \(A_K\) 单调递增,但是我们求值的式子里还有一个 \(F[j]\) 这个东西是单调递减的!!!!所以我们不能妄下决定,我们还需要映射一个取值的数据结构,也就是我们的 \(set\) ,我们要让这两个东西同时增加元素又同时减少相应的元素。



\(code:\)

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define inf 0x7fffffff
#define rg register int

using namespace std;

int n; ll m;
int a[100005];
ll f[100005];
int q[100005];
multiset<ll >s;

inline ll qr(){
    register char ch; register bool sign=0; ll res=0;
    while(!isdigit(ch=getchar())) if(ch=='-')sign=1;
    while(isdigit(ch)) res=res*10+(ch^48),ch=getchar();
    return sign?-res:res;
}

int main(){
    //freopen("in.in","r",stdin);
    //freopen("cpp.out","w",stdout);
    n=qr(); m=qr();
    for(rg i=1;i<=n;++i) a[i]=qr();
    rg l=1,r=0,k=1; ll tot=0;
    for(rg i=1;i<=n;++i){
        tot+=a[i];
        while(tot>m)tot-=a[k++];
        if(k>i){puts("-1");return 0;}
        while(l<=r&&a[q[r]]<=a[i]){
            if(r>l)s.erase(f[q[r-1]]+a[q[r]]);; --r;
        }q[++r]=i; if(r>l)s.insert(f[q[r-1]]+a[q[r]]);
        while(l<=r&&q[l]<k){
            if(r>l)s.erase(f[q[l]]+a[q[l+1]]);; ++l;
        }f[i]=f[k-1]+a[q[l]];
        if(l<r)f[i]=min(f[i],*s.begin());
    }printf("%lld\n",f[n]);
    return 0;
}