题解:P10977 Cut the Sequence

传送门

solution

\(dp_i\) 表示前 \(i\) 个已经分好段,有状态转移:\(dp_i = dp_{j - 1} + \displaystyle\max_{k=j}^{i}\{a_k\}\),满足 \(\displaystyle\sum^i_{k=j}{a_k} \leq m\) 前提下,复杂度 \(O(n^2)\),无法接受。

考虑优化,从单调性先入手,对于一个不变的 \(i\),当 \(j\) 单增时,\(\displaystyle\max_{k=j}^{i}\{a_k\}\) 单调不增,\(dp_{j-1}\) 单调不减,在我们枚举到 \(i\) 时,\(dp_{j-1}\) 并不会对转移产生影响。

考虑 \(a_i\) 对区间的影响,每次找到 \(a_{pos} > a_i\),其中 \(pos \in [1,i)\),显然可以发现对于 \(\displaystyle\max_{k=j}^{i}\{a_k\}(j > pos) \rightarrow a_i\),考虑线段树维护这个操作,同时维护 \(dp_i\) 的转移。

需要进行的操作如下:

  • 当前 \(j\)\(dp_{j-1}\),单点修改。
  • 上文提到的操作,区间修改。
  • \(dp_i\) 的转移,同时区间查询最小值。

每次二分找到满足条件的区间右端点的后继 \(ptr\),查询区间即为 \([ptr + 1,i]\),最终答案 \(dp_{n}\),完结撒花!

code

#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
#define int long long

using namespace std;
template <typename T>
inline void read(T &x){
    T res = 0,f = 1;
    char ch = getchar();
    while(!isdigit(ch)){if(ch == '-') f = -1;ch = getchar();}
    while(isdigit(ch)){res = (res << 1) + (res << 3) + (ch ^ 48);ch = getchar();}
    x = res * f;
}

const int maxn = 1e5 + 10,inf = 1e18;
int n,m,a[maxn],st[maxn],top,apr[maxn],dp[maxn],sum[maxn];
struct node{int l,r,Answer,lazy,dp;}tree[maxn << 4];

void pushup(int rt){
    tree[rt].Answer = min(tree[rt << 1].Answer,tree[rt << 1 | 1].Answer);
    tree[rt].dp = min(tree[rt << 1].dp,tree[rt << 1 | 1].dp);
}

void pushdown(int rt){
    if(tree[rt].lazy != inf){
        tree[rt << 1].Answer = tree[rt << 1].dp + tree[rt].lazy;
        // cout << tree[rt << 1].Answer << " " << tree[rt << 1].dp << " " << tree[rt].lazy << endl;
        tree[rt << 1 | 1].Answer = tree[rt << 1 | 1].dp + tree[rt].lazy;
        tree[rt << 1].lazy = tree[rt << 1 | 1].lazy = tree[rt].lazy;
        tree[rt].lazy = inf;
    }
}

void Build(int l,int r,int rt){
    tree[rt].l = l,tree[rt].r = r;
    if(l == r){
        tree[rt].Answer = tree[rt].dp = tree[rt].lazy = inf;
        return;
    }
    int mid = l + r >> 1;
    Build(l,mid,rt << 1);
    Build(mid + 1,r,rt << 1 | 1);
    pushup(rt);
}

void Update(int ql,int qr,int rt,int tag){
    int l = tree[rt].l,r = tree[rt].r;
    if(ql <= l && qr >= r)
    {
        tree[rt].Answer = tree[rt].dp + tag;
        tree[rt].lazy = tag;
        return;
    }
    pushdown(rt);
    int mid = l + r >> 1;
    if(ql <= mid)
        Update(ql,qr,rt << 1,tag);
    if(qr > mid)
        Update(ql,qr,rt << 1 | 1,tag);
    pushup(rt);
}

void Modify(int pos,int rt){
    int l = tree[rt].l,r = tree[rt].r;
    if(l == r){
        tree[rt].Answer = inf;
        tree[rt].dp = dp[pos - 1];
        return;
    }
    int mid = l + r >> 1;
    pushdown(rt);
    if(pos <= mid)
        Modify(pos,rt << 1);
    else
        Modify(pos,rt << 1 | 1);
    pushup(rt);
}

int query(int ql,int qr,int rt){
    int l = tree[rt].l,r = tree[rt].r;
    if(ql <= l && qr >= r)
        return tree[rt].Answer;
    pushdown(rt);
    int mid = l + r >> 1,val = inf;
    if(ql <= mid)
        val = min(val,query(ql,qr,rt << 1));
    if(qr > mid)
        val = min(val,query(ql,qr,rt << 1 | 1));
    return val;
}

inline void init(){
    read(n),read(m);
    for(int i = 1;i <= n;i ++) {
        read(a[i]),sum[i] = sum[i - 1] + a[i];
        if(a[i] > m){
            printf("-1\n");
            exit(0);
        }
    }
    st[++ top] = 1;
    for(int i = 2;i <= n;i ++){
        while(top && a[st[top]] < a[i]) top --;
        if(top) apr[i] = st[top];
        st[++ top] = i;
    }
    // 预处理 a[pos] > a[i]
    Build(1,n,1);
}

inline void Solve(){
    int Answer = inf;
    bool flagAns = 1;
    for(int i = 1;i <= n;i ++){
        Modify(i,1);
        if(apr[i] < i) Update(apr[i] + 1,i,1,a[i]);
        int ptr = lower_bound(sum,sum + i + 1,sum[i] - m) - sum;//  二分找到满足条件的下标
        if(ptr < i) dp[i] = query(ptr + 1,i,1);
        else{// 没有满足的无解
            flagAns = 0;
            break;
        }
    }
    if(!flagAns)
        printf("-1\n");
    else
        printf("%lld\n",dp[n]);
}

signed main(){
    init();
    Solve();
    return 0;
}
posted @ 2024-12-29 17:58  Alec_Ayaka  阅读(4)  评论(0编辑  收藏  举报