1 条题解

  • 0
    @ 2026-4-30 1:55:07

    提供另一种维护方式:使用分块,时间复杂度 O(nn)O(n\sqrt n),但是常数小。


    题意

    给定一个序列,每次询问单点修改,再求把区间 [l,r][l,r] 划分为若干段使每段的和构成的序列是交替的。


    性质分析

    这部分另外的题解已经讲得很详细了,但为了形成一篇完整的题解,就在这略提几句。

    首先发现在最优方案下,一定是划分成一些大段和一些长度为 1 的小段,因为如果小段长度 >1>1,把剩下的分到大段一定不劣。

    在最优方案下,合法大段的充要条件是 i=lrai>al1\sum_{i=l}^r a_i > a_{l-1}>ar+1>a_{r+1}。注意到在不保证最优条件下,这样划分也可以得到正确段数。考虑如下证明:假设一个长度 >1>1 的小段 [l,r][l,r],左侧有大段 [l1,r1][l_1,r_1],右侧大段 [l2,r2][l_2,r_2],若 al<ara_l<a_r,那么把右侧变为 [l+1,r2][l+1,r_2] 依然合法,反之同理拓展左边;若 [l,r][l,r] 在最左侧,若 a1<al21a_1<a_{l_2-1},那么把 l2l_2 移动到 22,否则新增一段 [1,l22][1,l_2-2],在最右边同理对称一下。

    成满足上述条件的段为好段,发现找好段划分满足最优条件下充要条件,一定可以找出不劣于最优解的答案,且已经证明这样求出的答案不会出现使答案偏大的不合法情况,于是只要计算好段划分即可。

    现在的问题变为了,有一些区间 [l,r][l,r],要选出一些区间使它们无交,最多选出多少个区间。可以贪心,按照右端点从小到大,尽量选择右端点小的。设 nxtinxt_i 表示若上一个好段结尾为 ii,那下一个可以接在它后面的好段的最小右端点。于是我们会 O(n2)O(n^2) 了,暴力修改 nxtnxt,然后询问时候暴力计算跳了多少次 nxtnxt。需要稍微讨论一下开头结尾是否存在小段,这也是简单的,因为若不存在小段,根据贪心思想第一个大段也被确定了。

    感觉每次只修改一位,把整个 nxtnxt 重算一遍过于浪费。发现有重要性质:nxtiinxt_i - iO(logV)O(\log V) 级别。考虑从下个好段中任意一点向左右拓展,每拓展一次需满足当前和 << 拓展的位置,也就是说和至少翻倍,因此 O(logV)O(\log V) 次拓展后和大于任意一个相邻的数。有这个性质之后,每次修改只会影响 O(logV)O(\log V) 个位置的 nxtnxt

    好像不是略提几句了


    快速跳 nxtnxt

    可以 LCT,但那太蠢了,而且我不会。也可以线段树,是 lognlogV\log n \log V 的,但是常数不小而且不是很好写。

    注意到这部分和 P3203 非常像。借鉴那题的分块,维护 tit_icic_i 表示从当前块内一点第一次跳到块外的位置和跳跃次数。

    修改的时候,假设 nxtinxt_i 发生变动的区间为 [l,r][l,r],首先修改 [l,r][l,r] 内的 ttcc,然后修改剩下的和 ll 在一个块内的位置的 ttcc,修改可以递推,单个位置 O(1)O(1),这部分最多修改 logV+n\log V + \sqrt n 和点。

    询问时候先跳 tt,再跳 nxtnxt,两段跳跃次数都不超过 n\sqrt n

    因此最后的复杂度是 O(nlog2V+nn)O(n \log^2 V + n\sqrt n),稍劣于线段树,但是常数小啊,一发交上去是目前的最优解。


    Code

    代码不长,但是有一些细节。

    #include<bits/stdc++.h>
    using namespace std;
    #define inf 0x3f3f3f3f
    #define mod 998244353
    #define N 250010
    #define int long long
    #define szi sizeof(int)
    #define il inline
    int n,q,sq,a[N],x,y,l,ll,r,rr,nxt[N],p[N],t[N],c[N],ans;
    il void cmax(int &x,int y){x=(x>y)?x:y;return ;}
    il void cmin(int &x,int y){x=(x<y)?x:y;return ;}
    il void upd(int l,int r){
        for(int i=r;i>=l;--i){
            nxt[i]=nxt[i+1];
            for(int j=i+2,s=a[i+2];j<=n&&j-i<=65;s+=a[++j])
                if(s>a[i+1]&&s>a[j+1]){cmin(nxt[i],j);break;}
            if(p[nxt[i]]==p[i]) t[i]=t[nxt[i]],c[i]=c[nxt[i]]+2;
            else t[i]=nxt[i],c[i]=2;
        }
        for(int i=l-1;i>=0&&p[i]==p[l];--i){
            if(p[nxt[i]]==p[i]) t[i]=t[nxt[i]],c[i]=c[nxt[i]]+2;
            else t[i]=nxt[i],c[i]=2;
        }
        return ;
    }
    il int calc(int l,int r){
        int res=0; --l; if(l>=r) return -inf;
        while(t[l]<r) res+=c[l],l=t[l]; while(nxt[l]<r) res+=2,l=nxt[l];
        return res;
    }
    signed main(){
        // freopen("a.in","r",stdin);
        // freopen("a.out","w",stdout);
        scanf("%lld",&n),sq=sqrt(n);
        for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
        for(int i=0;i<=n;++i) p[i]=i/sq+1; p[n+1]=p[n]+1;
        nxt[n+1]=n+1,upd(0,n),scanf("%lld",&q);
        while(q--){
            scanf("%lld%lld%lld%lld",&x,&y,&l,&r),++l,a[x]=y;
            upd(max(0ll,x-65),x-1),ans=1,ll=r+1,rr=l-1;
            for(int i=l,s=a[l];i<r;s+=a[++i]) if(s>a[i+1]){ll=i+1;break;}
            for(int i=r,s=a[r];i>l;s+=a[--i]) if(s>a[i-1]){rr=i-1;break;}
            cmax(ans,calc(l,r)+1),cmax(ans,calc(ll,r)+2);
            cmax(ans,calc(l,rr)+2),cmax(ans,calc(ll,rr)+3);
            printf("%lld\n",ans);
        }
        return 0;
    }
    
    • 1

    信息

    ID
    7483
    时间
    3000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者