YACS 2023年8月月赛 甲组 T3 金字塔分割 题解

发布时间 2023-08-22 21:30:51作者: Xy_top

看到这题,自然的想到 DP 啦!

如果设 $f_{i,j}$ 为到第 $i$ 个位置前面的都合法且最后一段和为 $j$ 是否可行,那么转移十分显然,但是状态会炸。

此时我们考虑在状态上进行优化来减少时间,把 $f_i$ 设为到第 $i$ 个位置分段数量最多的情况下且最后一段和最少的和,以及能分成几段就好了。

不难写出 $n^2$ 的代码:

#include<iostream>
using namespace std;
int n;
int a[200005],s[200005];
int f[200005][2];//满足分段尽可能多的情况下,最后一段和的最小值
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    for(int i=1;i<=n;i++){
        f[i][0]=1;
        f[i][1]=s[i];
    }
    for(int i=2;i<=n;i++){
        for(int j=1;j<i;j++){
            if(s[i]-s[j]>=f[j][1]&&f[j][0]+1>=f[i][0]){
                if(f[j][0]+1==f[i][0])f[i][1]=min(f[i][1],s[i]-s[j]);
                else{
                    f[i][0]=f[j][0]+1;
                    f[i][1]=s[i]-s[j];
                }
            }
        }
    }
    cout<<f[n][0];
    return 0;
}

接着,我们根据那一行 if 的不等式,进行变换得到 ${s_i}>={s_j}+f_{j,1}$,所以思路显然。

算完 $f_i$ 后,把 $i$ 加到 $s_i+f_{i,1}$ 上,查询就查询 $<=s_i$ 位置上最大的 $f_{x,0}$,如果有多个相同的,那么取 $x$ 最大的,这样一来最后一段的和才能最小。

代码如下:(tjq 说可以单调队列,但对热爱线段树的我来说这样就够了吧。)

#include<iostream>
using namespace std;
const int inf=2000200000;
int n,rt,cnt;
int a[200005],s[200005];
int f[200005][2];//满足分段尽可能多的情况下,最后一段和的最小值
int mx[7000005],ls[7000005],rs[7000005];//单点修改,区间查询
bool cmp(int i,int j){//i 是否比 j 好
    if(i==0)return false;
    if(j==0)return true;
    if(f[i][0]<f[j][0])return false;
    else if(f[i][0]==f[j][0]){
        if(i<j)return false;
        return true;
    }
    return true;
}
void update(int l,int r,int &k,int x,int y){
    if(!k)k=++cnt;
    if(l==r){
        if(!cmp(mx[k],y))mx[k]=y;
        return;
    }
    int mid=l+r>>1;
    if(x<=mid)update(l,mid,ls[k],x,y);
    else update(mid+1,r,rs[k],x,y);
    if(cmp(mx[ls[k]],mx[rs[k]]))mx[k]=mx[ls[k]];
    else mx[k]=mx[rs[k]];
}
int query(int l,int r,int k,int x,int y){
    if(x<=l&&y>=r)return mx[k];
    int mid=l+r>>1,res=0;
    if(x<=mid)res=query(l,mid,ls[k],x,y);
    if(y>mid){
        int tmp=query(mid+1,r,rs[k],x,y);
        if(cmp(tmp,res))res=tmp;
    }
    return res;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        s[i]=s[i-1]+a[i];
    }
    for(int i=1;i<=n;i++){
        int j=query(1,inf,1,1,s[i]);
        f[i][0]=f[j][0]+1;
        f[i][1]=s[i]-s[j];
//        for(int j=1;j<i;j++){
//            if(s[i]-s[j]>=f[j][1]&&f[j][0]+1>=f[i][0]){
//                if(f[j][0]+1==f[i][0])f[i][1]=min(f[i][1],s[i]-s[j]);
//                else{
//                    f[i][0]=f[j][0]+1;
//                    f[i][1]=s[i]-s[j];
//                }//s[i]>=s[j]+f[j][1]
//                //算完一个后,将 f[j][0] 的 s[j]+f[j][1] 加到 xds 中,查询 <= s[i] 位置上的最大值
//            }
//        }
        update(1,inf,rt,s[i]+f[i][1],i);
    }
    cout<<f[n][0];
    return 0;
}