1 条题解

  • 0
    @ 2025-8-24 21:46:55

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar Kelin
    这个家伙太菜,没什么可以留下的

    搬运于2025-08-24 21:46:55,当前版本为作者最后更新于2018-04-01 00:55:16,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题意

    给你一个序列,每次询问一个区间,求其所有子区间的最小值之和


    题解

    这里有两种方法,一种是离线的莫队,一种是在线算法

    一.莫队

    这题难就难在怎么由[l,r][l,r]推向[l,r+1][l,r+1]

    考虑他们之间的增量就是新增的[l,r+1],[l+1,r+1],,[r+1,r+1][l,r+1],[l+1,r+1],\ldots,[r+1,r+1]rl+2r-l+2个区间的最小值之和

    考虑求出[l,r+1][l,r+1]的最小值位置是pp,那么所有左端点在[l,p][l,p]之间的区间答案都是a[p]a[p]

    贡献就是a[p]×(pl+1)a[p]\times(p-l+1),这一部分可以用rmqrmq求最小值处理

    考虑剩下的左端点在[p+1,r+1][p+1,r+1]的区间

    f[l][r]f[l][r]表示以rr为右端点,左端点在[l,r][l,r]的区间的答案(要求的就是f[p+1][r+1]f[p+1][r+1])

    记录一下preipre_i表示从ii向前第一个比ii小的数的位置(这个可以用单调栈O(n)O(n)求出)

    那么左端点在[prer,r][pre_r,r]的区间最小值都是a[r]a[r]

    那么就有f[l][r]=f[l][prer]+ar×(rprer)f[l][r]=f[l][pre_r]+a_r\times(r-pre_r)

    可以发现dpdp增量只和rr自身有关,所以可以去掉ll那一维

    因为最终一定会存在一个点xx,满足prex=ppre_x=p

    那么$f_{r+1}=a_{r+1}\times(r+1-pre_{r+1})+\ldots+a_x\times(x-p)+f_p$

    我们可以发现fr+1fpf_{r+1}-f_p就是原来要求的f[p+1][r+1]f[p+1][r+1]

    这样我们就可以预处理出ff,然后就可以O(1)O(1)完成转移了

    删除的话我们就减去[l,r1][l,r][l,r-1]\to[l,r]的增量就好了

    至于在左边加,就对称处理就好了

    复杂度O(nlogn+nn)O(n\log n+n\sqrt n)

    (rmqrmqqryqryR(1<<t)+1R-(1<<t)+1的"+1+1”,忘记打了调了一个小时//一脸悲伤)

    #include<bits/stdc++.h>
    #define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
    #define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
    #define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
    #define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
    template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
    using namespace std;
    char ss[1<<17],*A=ss,*B=ss;
    inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
    template<class T>inline void sd(T&x){
        char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
        while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
    }
    char sr[1<<21],z[20];int C=-1,Z;
    inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
    template<class T>inline void we(T x){
        if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
        while(z[++Z]=x%10+48,x/=10);
        while(sr[++C]=z[Z],--Z);sr[++C]='\n';
    }
    const int N=1e5+5,inf=2e9;
    typedef int arr[N];
    typedef long long ll;
    struct Q{
        int l,r,x,id;
        inline bool operator<(const Q b)const{return x==b.x?x&1?r<b.r:r>b.r:x<b.x;}
    }q[N];
    int n,m,Sz,Top,Mi[17],f[N][17];arr a,pre,suf,S,Log;ll Now,fl[N],fr[N],ans[N];
    inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
    inline int qry(int L,int R){int t=Log[R-L+1];return cmp(f[L][t],f[R-Mi[t]+1][t]);}
    inline ll left(int L,int R){int p=qry(L-1,R);return (ll)a[p]*(R-p+1)+fl[L-1]-fl[p];}
    inline ll right(int L,int R){int p=qry(L,R+1);return (ll)a[p]*(p-L+1)+fr[R+1]-fr[p];}
    int main(){
        #ifndef ONLINE_JUDGE
            file("s");
        #endif
        sd(n);sd(m);Sz=sqrt(n);a[n+1]=a[0]=inf;
        Mi[0]=1;fp(i,1,16)Mi[i]=Mi[i-1]<<1;
        fp(i,2,n)Log[i]=Log[i>>1]+1;
        fp(i,1,n)sd(a[i]),f[i][0]=i;
        fp(j,1,Log[n])fp(i,1,n-Mi[j-1]+1)
            f[i][j]=cmp(f[i][j-1],f[i+Mi[j-1]][j-1]);
        fp(i,1,n){
            while(Top&&a[S[Top]]>a[i])suf[S[Top--]]=i;
            pre[i]=S[Top];S[++Top]=i;
        }while(Top)pre[S[Top]]=S[Top-1],suf[S[Top--]]=n+1;
        fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]];
        fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]];
        int x,y,L,R;
        fp(i,1,m)sd(x),sd(y),q[i]={x,y,x/Sz,i};
        sort(q+1,q+m+1);L=q[1].l,R=L-1;
        fp(i,1,m){
            x=q[i].l,y=q[i].r;
            while(L>x)Now+=left(L,R),L--;
            while(R<y)Now+=right(L,R),R++;
            while(L<x)Now-=left(L+1,R),++L;
            while(R>y)Now-=right(L,R-1),--R;
            ans[q[i].id]=Now;
        }
        fp(i,1,m)we(ans[i]);
    return Ot(),0;
    }
    

    二.在线算法

    我们假设区间[l,r][l,r]的最小值的位置是pp

    那么对于左端点在[l,p][l,p],右端点在[p,r][p,r]的区间最小值都是a[p]a[p]

    这一部分的贡献是a[p]×(pl+1)×(rp+1)a[p]\times(p-l+1)\times(r-p+1)

    我们还没有统计[l,p1][l,p-1][p+1,r][p+1,r]的答案

    在莫队算法中我们已经知道

    因为最终一定会存在一个点xx,满足prex=ppre_x=p

    那么$f_{r+1}=a_{r+1}\times(r+1-pre_{r+1})+\ldots+a_x\times(x-p)+f_p$

    我们可以发现fr+1fpf_{r+1}-f_p就是原来要求的f[p+1][r+1]f[p+1][r+1]

    然而这个是以r+1r+1为右端点,左端点在(p,r+1](p,r+1]的答案

    在这里我们就要考虑左端点在(p,x](p,x],右端点在[x,r][x,r]的全部答案

    对于点rr,所有以rr为右端点,左端点在(p,r](p,r]的区间答案是frfpf_r-f_p

    对于点r1r-1,所有以r1r-1为右端点,左端点在(p,r1](p,r-1]的区间答案是fr1fpf_{r-1}-f_p

    \ldots

    对于点p+1p+1,所有以p+1p+1为右端点,左端点在(p,p+1](p,p+1]的区间答案是fp+1fpf_{p+1}-f_p

    gi=j=1ifjg_i=\sum_{j=1}^if_j,通过观察发现,这一部分的答案就是grgpfp×(rp)g_r-g_p-f_p\times(r-p)

    同样的,pp左边的情况也是类似

    复杂度O(nlogn)O(n\log n)

    #include<bits/stdc++.h>
    #define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
    #define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
    #define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
    #define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
    template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
    using namespace std;
    char ss[1<<17],*A=ss,*B=ss;
    inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
    template<class T>inline void sd(T&x){
        char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
        while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
    }
    char sr[1<<21],z[20];int C=-1,Z;
    inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
    template<class T>inline void we(T x){
        if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
        while(z[++Z]=x%10+48,x/=10);
        while(sr[++C]=z[Z],--Z);sr[++C]='\n';
    }
    const int N=1e5+5,inf=2e9;
    typedef int arr[N];
    typedef long long ll;
    int n,m,Top,Mi[17],f[N][17];arr a,pre,suf,S,Log;ll fl[N],fr[N],gl[N],gr[N];
    inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
    inline int qry(int L,int R){int t=Log[R-L+1];return cmp(f[L][t],f[R-Mi[t]+1][t]);}
    int main(){
        #ifndef ONLINE_JUDGE
            file("s");
        #endif
        sd(n);sd(m);a[n+1]=a[0]=inf;
        Mi[0]=1;fp(i,1,16)Mi[i]=Mi[i-1]<<1;
        fp(i,2,n)Log[i]=Log[i>>1]+1;
        fp(i,1,n)sd(a[i]),f[i][0]=i;
        fp(j,1,Log[n])fp(i,1,n-Mi[j-1]+1)
            f[i][j]=cmp(f[i][j-1],f[i+Mi[j-1]][j-1]);
        fp(i,1,n){
            while(Top&&a[S[Top]]>a[i])suf[S[Top--]]=i;
            pre[i]=S[Top];S[++Top]=i;
        }while(Top)pre[S[Top]]=S[Top-1],suf[S[Top--]]=n+1;
        fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]],gr[i]=gr[i-1]+fr[i];
        fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]],gl[i]=gl[i+1]+fl[i];
        int l,r,p;
        while(m--){
            sd(l),sd(r),p=qry(l,r);
            we((ll)(p-l+1)*(r-p+1)*a[p]+
                gr[r]-gr[p]-fr[p]*(r-p)+
    			gl[l]-gl[p]-fl[p]*(p-l));
        }
    return Ot(),0;
    }
    

    到这里我们可以发现,这个算法的复杂度瓶颈就在于rmqrmq

    朴素倍增rmqrmq预处理速度太慢

    我们可以O(n)O(n)建立笛卡尔树来进行rmqrmq,设单次询问复杂度=k<logn=k\lt\log n

    所以这题可以做到O(n+km)O(n+km)

    #include<bits/stdc++.h>
    #define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
    #define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
    #define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
    #define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
    template<class T>inline bool cmax(T&a,const T&b){return a<b?a=b,1:0;}
    template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
    using namespace std;
    char ss[1<<17],*A=ss,*B=ss;
    inline char gc(){return A==B&&(B=(A=ss)+fread(ss,1,1<<17,stdin),A==B)?-1:*A++;}
    template<class T>inline void sd(T&x){
        char c;T y=1;while(c=gc(),(c<48||57<c)&&c!=-1)if(c==45)y=-1;x=c-48;
        while(c=gc(),47<c&&c<58)x=x*10+c-48;x*=y;
    }
    char sr[1<<21],z[20];int C=-1,Z;
    inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
    template<class T>inline void we(T x){
        if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
        while(z[++Z]=x%10+48,x/=10);
        while(sr[++C]=z[Z],--Z);sr[++C]='\n';
    }
    const int N=1e5+5,inf=2e9;
    typedef int arr[N];
    typedef long long ll;
    int n,m,rt;arr a,lc,rc,pre,suf,S,Log;ll fl[N],fr[N],gl[N],gr[N];
    inline int cmp(const int x,const int y){return a[x]<a[y]?x:y;}
    inline int qry(int L,int R){for(int x=rt;;x=(x>R?lc:rc)[x])if(L<=x&&x<=R)return x;}
    int main(){
        #ifndef ONLINE_JUDGE
            file("s");
        #endif
        sd(n);sd(m);a[n+1]=a[0]=inf;int*Top=S;
        fp(i,1,n){
            sd(a[i]);
            while(Top!=S&&a[*Top]>=a[i])lc[i]=*Top--;
            rc[*Top]=i;*++Top=i;
        }rt=S[1];int top=0;
        fp(i,1,n){
            while(top&&a[S[top]]>a[i])suf[S[top--]]=i;
            pre[i]=S[top];S[++top]=i;
        }while(top)pre[S[top]]=S[top-1],suf[S[top--]]=n+1;
        fp(i,1,n)fr[i]=(ll)a[i]*(i-pre[i])+fr[pre[i]],gr[i]=gr[i-1]+fr[i];
        fd(i,n,1)fl[i]=(ll)a[i]*(suf[i]-i)+fl[suf[i]],gl[i]=gl[i+1]+fl[i];
        int l,r,p;
        while(m--){
            sd(l),sd(r),p=qry(l,r);
            we((ll)(p-l+1)*(r-p+1)*a[p]+
                gr[r]-gr[p]-fr[p]*(r-p)+
    			gl[l]-gl[p]-fl[p]*(p-l));
        }
    return Ot(),0;
    }
    

    O(nlogn+nn)O(nlogn)O(n+km)O(n\log n+n\sqrt n)\to O(n\log n)\to O(n+km)可见这题多么毒瘤一般的优秀

    貌似可以出一道加强版(强制在线,数据范围n,m2×106n,m\le 2\times 10^6)


    upd:2018.4.5upd:2018.4.5

    唔~笛卡尔树被hackhack

    看来这题还是只能做到O(nlogn)O(n\log n)

    感谢@兔子接烧饼 指出我程序的错误

    • 1

    信息

    ID
    2319
    时间
    2000ms
    内存
    500MiB
    难度
    6
    标签
    递交数
    0
    已通过
    0
    上传者