P10977 Cut the Sequence 题解

link

大雕队列?不会不会。
线段树? YE5!YE5!

一个暴力的 \(dp\) 随便想:设 \(dp_i\) 表示考虑到序列第 \(i\) 个位置时的最小代价。
转移时:

\[dp_i=\min_{0\le j < i,\large \sum_{j+1}^{i} a_i \le m } \{dp_j+ \max_{j+1\le k \le i } \{a_k\}\} \]

一个 \(O(n^3)\) 的爆炸 \(dp\)!尝试油画!

\(m\) 的限制先不看,一看感觉就是双指针能随便搞的东西。(实际上也是)

式子的后半部分贡献的长相是一个后缀 \(\max\),还需要我们动态维护,每次拓展一个位置,我们可以通过维护区间最小值再进行线段树上二分,查找出一个 \(pos\) 满足 \(a_{pos} \le a_i\) 且是最靠左的一个,那么现在再把 \([pos,i]\) 区间内的位置做决策点时,后半部分的贡献都是 \(a_i\),所以在线段树上进行区间覆盖修改为 \(a_i\)

发现我们还有一个决策点上的 \(dp\) 值没有维护上,如果直接附加在了线段树上,那区间覆盖的时候该怎么维护?实际上 \(dp\) 值是非严格递增的,而我们线段树上维护的是最小值,所以对于一个区间在 \([l,r]\) 的节点进行区间覆盖为 \(v\) 后,我们可以直接把答案值修改为 \(dp_{l-1}+v\)

最后来看 \(m\) 的限制,哦,确实是双指针。

总时间复杂度 \(O(n \log n)\)

点击查看代码
const int N=1e5+5;
const int inf=1e18;
int n,m,a[N],sum[N],dp[N];
int mn[N*3],t[N*3],tag[N*3],P=1,DEP;
void pushup(int i){
    mn[i]=min(mn[ls(i)],mn[rs(i)]);
    t[i]=min(t[ls(i)],t[rs(i)]);
}
void push(int i,int v,int dep){
    mn[i]=v,tag[i]=v;
    int l=(i<<dep)-P;
    if(l-1>=0) t[i]=dp[l-1]+v;
}
void pushdown(int i,int dep){
    if(tag[i]!=-1){
        push(ls(i),tag[i],dep-1);
        push(rs(i),tag[i],dep-1);
        tag[i]=-1;
    }
}
int search(int v){
    int dep=0,i=1;
    while(dep<DEP){
        pushdown(i,DEP-dep);
        if(mn[ls(i)]<=v) i=i<<1;
        else i=i<<1|1;
        dep++;
    }
    return i-P;
}
void update(int l,int r,int v){
    l+=P-1,r+=P+1;
    int dep=0;
    for(int i=DEP;i;i--) pushdown(l>>i,i),pushdown(r>>i,i);
    while(l^1^r){
        if(~l&1) push(l^1,v,dep);
        if(r&1) push(r^1,v,dep);
        l>>=1,r>>=1,dep++;
        pushup(l),pushup(r);
    }
    for(l>>=1;l;l>>=1) pushup(l);
}
int query(int l,int r){
    l+=P-1,r+=P+1;
    int res=inf;
    for(int i=DEP;i;i--) pushdown(l>>i,i),pushdown(r>>i,i);
    while(l^1^r){
        if(~l&1) res=min(res,t[l^1]);
        if(r&1) res=min(res,t[r^1]);
        l>>=1,r>>=1;
    }
    return res;
}
void xpigeon(){
    rd(n,m);
    for(int i=1;i<=n;i++){
        rd(a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    while(P<=n+1) P<<=1,DEP++;
    memset(tag,-1,sizeof(tag));
    for(int i=1,l=1;i<=n;i++){
        while(l<=i && sum[i]-sum[l-1]>m) l++;
        if(l>i) {cout<<-1;return ;}
        int pos=search(a[i]);
        update(pos,i,a[i]);
        dp[i]=query(l,i);
    }
    cout<<dp[n]<<'\n';
}
signed main() {
    xpigeon();
    return 0;
}

可能后续会补一个单调队列的做法。

posted @ 2025-09-25 14:33  香香的鸽子  阅读(10)  评论(0)    收藏  举报