zoukankan      html  css  js  c++  java
  • 【题解】【原创题目】伊卡洛斯和西瓜

    【题解】【原创题目】伊卡洛斯和西瓜

    出题人:辰星凌

    验题人:( ext{YudeS}) / ( ext{LEGENDARY_CREATOR})

    题目传送门:伊卡洛斯和西瓜

    【题目描述】

    简化题意:给出 (X,Y) 和一个长度为 (n) 的序列 (a),对于每次询问 (Li,Ri),求 (sum_{Li leqslant l leqslant r leqslant Ri,(S_r-S_{l-1})in [X,Y]} 1),其中 (S)(a) 的前缀和数组。

    【分析】

    【Subtask #1】

    纯暴力模拟一下就好了,没啥可说的。

    注意开 (long long)

    时间复杂度:(O(Tn^2))

    分数:(10pt)

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #define LL long long
    #define Re register LL
    using namespace std;
    const int N=1e6+5;
    LL n,x,y,T,X,Y,a[N],S[N];
    inline void in(Re &x){
        Re fu=0;x=0;char ch=getchar();
        while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
        while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        x=fu?-x:x;
    }
    int main(){
        in(n),in(T),in(X),in(Y);
        for(Re i=1;i<=n;++i)in(a[i]),S[i]=S[i-1]+a[i];
        while(T--){
            in(x),in(y);LL ans=0;
            for(Re i=x;i<=y;++i)
                for(Re j=i;j<=y;++j)
                    ans+=(X<=S[j]-S[i-1]&&S[j]-S[i-1]<=Y);
            printf("%lld
    ",ans);
        }
    }
    

    【优化】

    可以提前预处理出所有答案,然后直接查询。

    时间复杂度:(O(n^2+T))

    分数:(10pt)

    代码略。

    【Subtask #2】

    考虑分为两步解决:

    [ans=(sum_{Li leqslant l leqslant r leqslant Ri,S_r-S_{l-1} leqslant Y} 1) - (sum_{Li leqslant l leqslant r leqslant Ri,S_r-S_{l-1} leqslant X-1} 1) ]

    现在的问题就是求这个式子:

    [sum_{Li leqslant l leqslant r leqslant Ri,S_r-S_{l-1} leqslant X} 1 ]

    我们把它变成:

    [sum_{Li-1 leqslant l < r leqslant Ri,S_r-S_{l} leqslant X} 1 ]

    其中 (S_r-S_{l} leqslant X) 又可以化为如下两种形式:(S_r leqslant S_l+X,) (S_r-X leqslant S_l)

    沿用上面 (Subtask 1) 中的暴力枚举法,开一颗权值树状数组维护当前已经加入的 (S),将第二层循环优化成 (logn)

    时间复杂度:(O(Tnlogn))

    分数:(10pt)

    代码略。

    【优化】

    题目没有要求强制在线,可以考虑莫队暴力搞,在 (add,del) 函数调用区间查询和单点修改即可,具体实现如下:

    • 在左指针 (l) 向左移动后,(ans) 加上树状数组中小于等于 (S_l+X) 的数量,然后将 (S_l) 丢进去。

    • 在左指针 (l) 向右移动后,将 (S_l) 抠掉,然后 (ans) 减去树状数组中小于等于 (S_l+X) 的数量。

    • 在右指针 (r) 向右移动后,(ans) 加上树状数组中大于等于 (S_r-X) 的数量,然后将 (S_r) 丢进去。

    • 在右指针 (r) 向左移动后,将 (S_r) 抠掉,然后 (ans) 减去树状数组中大于等于 (S_r-X) 的数量。

    注意在 (add) 函数中是先更新 (ans) 再加入 (S)(del) 是先抠掉 (S) 再更新 (ans) 。代码如下:

    #define lo(_) (lower_bound(b+1,b+m+1,_)-b)
    inline void addL(Re l){ans+=T1.ask(1,lo(S[l]+X)),T1.add(lo(S[l]),1);}
    inline void delL(Re l){T1.add(lo(S[l]),-1),ans-=T1.ask(1,lo(S[l]+X));}
    inline void addR(Re r){ans+=T1.ask(lo(S[r]-X),n),T1.add(lo(S[r]),1);}
    inline void delR(Re r){T1.add(lo(S[r]),-1),ans-=T1.ask(lo(S[r]-X),n);}
    

    发现 (lower\_bound) 的使用平白无故地多出了一个 (log),用预处理把它去掉(QAQ不预处理的代码 (Subtask 2) 会全部 (TLE))。

    时间复杂度:(O(n sqrt{n} logn))

    分数:(60pt)

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define Re register LL
    #define lo(_) (lower_bound(b+1,b+m+1,_)-b)
    using namespace std;
    const int N=1e6+5,M=1e6+3;
    LL n,m,x,y,T,X,Y,BL,a[N],b[N*5],S[N],rk[N][5];
    //b: 离散化数组(注意要开5倍)
    //S: a的前缀和数组
    //rk[i][0]: S[i]离散化后的排名
    //rk[i][1]: S[i]-Y离散化后的排名
    //rk[i][2]: S[i]+Y离散化后的排名
    //rk[i][3]: S[i]-(X-1)离散化后的排名
    //rk[i][4]: S[i]+(X-1)离散化后的排名
    inline void in(Re &x){
        Re fu=0;x=0;char ch=getchar();
        while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
        while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        x=fu?-x:x;
    }
    inline void print(Re x){if(x>9)print(x/10);putchar('0'+x%10);}
    struct Query{
        LL l,r,id;
        inline bool operator<(Query O)const{
            Re B1=l/BL,B2=O.l/BL;
            return B1!=B2?B1<B2:((B1&1)?r<O.r:r>O.r);//奇偶性排序
        }
    }Q[M];
    struct BIT{
        LL n,C[N*5];//空间要开离散化后的权值大小
        inline void CL(){for(Re i=0;i<=n;++i)C[i]=0;}
        inline void add(Re x,Re v){while(x<=n)C[x]+=v,x+=x&-x;}
        inline LL ask_(Re x){LL ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}
        inline LL ask(Re L,Re R){return ask_(R)-ask_(L-1);}
    }T1;
    struct Sovle{
        LL o,X,ans,Ans[M];
        inline void addL(Re l){ans+=T1.ask(1,rk[l][o+2]),T1.add(rk[l][0],1);}//S[r]<=S[l]+X
        inline void delL(Re l){ans-=T1.ask(1,rk[l][o+2])-1,T1.add(rk[l][0],-1);}//S[r]<=S[l]+X (注意ask时要减去S[l]本身)
        inline void addR(Re r){ans+=T1.ask(rk[r][o+1],m),T1.add(rk[r][0],1);}//S[r]-X<=S[l]
        inline void delR(Re r){ans-=T1.ask(rk[r][o+1],m)-1,T1.add(rk[r][0],-1);}//S[r]-X<=S[l] (注意ask时要减去S[r]本身)
        inline void sakura(){
            T1.CL();
            Re nowL=1-1,nowR=0-1;//由于位置0算了进来,所以莫队指针起点要减一
            for(Re i=1;i<=T;++i){
                Re L=Q[i].l,R=Q[i].r;
                while(nowR<R)addR(++nowR);
                while(nowR>R)delR(nowR--);
                while(nowL>L)addL(--nowL);
                while(nowL<L)delL(nowL++);
                Ans[Q[i].id]=ans;
            }
        }
    }S1,S2;
    int main(){
        in(n),in(T),in(X),in(Y),BL=sqrt(n)+1,S1.X=Y,S2.X=X-1,S1.o=0,S2.o=2;
        for(Re i=1;i<=n;++i)in(a[i]),S[i]=S[i-1]+a[i];
        for(Re i=0;i<=n;++i)b[++m]=S[i],b[++m]=S[i]+Y,b[++m]=S[i]+(X-1),b[++m]=S[i]-Y,b[++m]=S[i]-(X-1);//将可能用到的值都存进去离散化
        sort(b+1,b+m+1);m=unique(b+1,b+m+1)-b-1;T1.n=m;//注意树状数组中的权值上界
        for(Re i=0;i<=n;++i)rk[i][0]=lo(S[i]),rk[i][1]=lo(S[i]-Y),rk[i][2]=lo(S[i]+Y),rk[i][3]=lo(S[i]-(X-1)),rk[i][4]=lo(S[i]+(X-1));//预处理排名
        for(Re i=1;i<=T;++i)in(Q[i].l),in(Q[i].r),--Q[i].l,Q[i].id=i;//这里把Q[i].l减1,方便后面处理
        sort(Q+1,Q+T+1);
        S1.sakura(),S2.sakura();
        for(Re i=1;i<=T;++i)print(S1.Ans[i]-S2.Ans[i]),puts("");
    }
    

    【Subtask #3】

    注意 (a_i) 为正整数,这意味着什么?

    如果不考虑 (Li,Ri) 的限制,对于每个 (r),以它为右端点的合法区间的 左端点必定是一段连续的位置。

    (AL[i]) 表示满足 (S_i-S_{j} leqslant Y)最靠左边(j)

    (AR[i]) 表示满足 (S_i-S_{j} geqslant X)最靠右边(j)

    如图,红线指向的位置 (j)均满足 (S_i-S_j leqslant Y),蓝线指向的位置 (j)均满足 (S_i-S_j geqslant X)

    易知两区间 ([AL[i],i])([1,AR[i]]) 的交集即是以 (i) 为右端点的合法区间左端点集合。

    而随着 (i) 的增大,(AL[i],AR[i]) 均单调不下降,直接两个指针 (O(n)) 扫过去就可以求出来。

    (Seg_i) 表示区间 ([AL[i],AR[i]]),由于 (Seg_i) 单调不下降,每次询问 ([L,R]) 时,对 (Seg_L,Seg_{L+1}...Seg_{R}) 做一遍 (O(n)) 的线段覆盖,可以得到右端点在 ([L,R]) 以内的所有合法左端点,统计其中大于等于 (L) 的部分即可。

    时间复杂度:(O(Tn))

    分数:(60pt)

    代码略。

    【优化】

    依旧是考虑离线。

    先将询问按右端点从小到大排序,用一个指针 (p) 表示当前已经加入前 (p) 个区间 (Seg),用线段树维护对于每个位置的合法右端点个数,每当指针向右移动时,就把区间 (Seg_i) 中的数全部加 (1) 。查询时统计区间 ([L,R]) 中的个数总和即可。 (由于 (Seg_i) 必定小于等于 (i),所以把 (Seg_1,Seg_2...Seg_{L-1}) 的加入是不会对询问产生影响的)

    时间复杂度:(O(nlogn))

    分数:(100pt)

    【Code】

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define Re register int
    #define lo(_) (lower_bound(b+1,b+m+1,_)-b)
    using namespace std;
    const int N=1e6+5,M=1e6+3;
    int n,m,x,y,T,a[N],AL[N],AR[N];LL X,Y,S[N],Ans[M];
    inline void in(Re &x){
        Re fu=0;x=0;char ch=getchar();
        while(ch<'0'||ch>'9')fu|=ch=='-',ch=getchar();
        while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
        x=fu?-x:x;
    }
    inline void print(LL x){if(x>9)print(x/10);putchar('0'+x%10);}
    struct Segment_Tree{
        #define pl p<<1
        #define pr p<<1|1
        #define mid (L+R>>1)
        struct QAQ{int tag;LL S;}tr[N<<2];
        inline void updata(Re p,Re L,Re R,Re v){tr[p].S+=(LL)v*(R-L+1),tr[p].tag+=v;}
        inline void pushdown(Re p,Re L,Re R){
            if(tr[p].tag){
                updata(pl,L,mid,tr[p].tag);
                updata(pr,mid+1,R,tr[p].tag);
                tr[p].tag=0;
            }
        }
        inline void pushup(Re p){tr[p].S=tr[pl].S+tr[pr].S;}
        inline void change(Re p,Re L,Re R,Re l,Re r,Re v){
            if(l>r)return;//注意判断
            if(l<=L&&R<=r){updata(p,L,R,v);return;}
            pushdown(p,L,R);
            if(l<=mid)change(pl,L,mid,l,r,v);
            if(r>mid)change(pr,mid+1,R,l,r,v);
            pushup(p);
        }
        inline LL ask(Re p,Re L,Re R,Re l,Re r){
            if(l<=L&&R<=r)return tr[p].S;
            LL ans=0;pushdown(p,L,R);
            if(l<=mid)ans+=ask(pl,L,mid,l,r);
            if(r>mid)ans+=ask(pr,mid+1,R,l,r);
            return ans;
        }
    }T1;
    struct Query{int l,r,id;inline bool operator<(Query O)const{return r<O.r;}}Q[M];//询问按右端点排序
    inline void sakura(){
        Re L=1,R=0;
        for(Re i=1;i<=n;++i){
            while(L<=i&&S[i]-S[L-1]>Y)++L;//若L不合法,右移指针
            while(R<i&&S[i]-S[R+1-1]>=X)++R;//若R+1合法,右移指针
            AL[i]=L,AR[i]=R;
        }
        Re now=0;
        for(Re i=1;i<=T;++i){
            while(now<Q[i].r)++now,T1.change(1,1,n,AL[now],AR[now],1);//右移指针至查询处
            Ans[Q[i].id]=T1.ask(1,1,n,Q[i].l,now);//统计左端点大于等于Q[i].l的区间数
        }
    }
    int main(){
        in(n),in(T),scanf("%lld%lld",&X,&Y);
        for(Re i=1;i<=n;++i)in(a[i]),S[i]=S[i-1]+a[i];
        for(Re i=1;i<=T;++i)in(Q[i].l),in(Q[i].r),Q[i].id=i;
        sort(Q+1,Q+T+1),sakura();
        for(Re i=1;i<=T;++i)print(Ans[i]),puts("");
    }
    

    【一些题外话】

    感谢 ( ext{YudeS}) 提出的题解修改建议!

    第一次出原创题,花了接近一整天的时间,感觉快要累死了。

    首先是码三个针对不同数据的 (std),因为一些奇奇怪怪的小细节调了好长时间。

    然后就是等待无聊的对拍,在此期间发现 (U) 盘莫名坏掉了,急急忙忙下了一堆数据恢复软件,结果都要充钱F**k 。

    此时发现对于 (X leqslant 0) 的数据后两种做法都会挂,心情差到了极点,懒得去深究原因了,直接把数据全放成了大于 (1) 的正整数,然后就开始造大数据(或许有大佬能够指出思路或者代码上的错误?)。

    造完数据就已经快 (21) 点了,结果大小超限?于是又砍掉了一半,只留下 (10) 个测试点,压缩后 (62Mb),试探性地上传一下居然过了。

    最后就是瞎编题面和写题解了,这项工作拖到第二天下午才完成。。。

  • 相关阅读:
    ThreadLocal
    mysql查看和修改密码策略
    synchronized双重校验问题
    多线程下双重检查锁的问题及解决方法
    compiler explorer网站
    隐藏表格部分内容,开启宏自动显现
    powershell系列学习笔记二:变量
    强制用户启用宏
    poweshell系列学习笔记一:基础
    Cobalt Strike修改证书
  • 原文地址:https://www.cnblogs.com/Xing-Ling/p/12114426.html
Copyright © 2011-2022 走看看