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

题目链接

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

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

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

不难写出 n2 的代码:

复制代码
#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 的不等式,进行变换得到 si>=sj+fj,1,所以思路显然。

算完 fi 后,把 i 加到 si+fi,1 上,查询就查询 <=si 位置上最大的 fx,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;
}
复制代码

 

posted @   Xy_top  阅读(75)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示