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

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

出题人:辰星凌

验题人:\(\text{YudeS}\) / \(\text{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\n",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("");
}

【一些题外话】

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

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

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

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

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

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

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

posted @ 2019-12-29 11:06  辰星凌  阅读(474)  评论(0编辑  收藏  举报