P9212 「蓬莱人形」

题外话#

  • 是图老师推的题捏。

  • 第 70 道紫,纪念一下。

  • 写这题写了好久啊,都要调崩溃了,终于过了/ll

  • 之前一直没尝试过写值域分块,今天第一次写竟然没出锅。


Links#

原题传送门


题意#

给定一个长度为 n (n105) 的序列 a (1ai105),你需要回答 q (q5×105) 次询问(非强制在线),每次询问给出参数 l,r,x,y,m (1lrn,1x,y,m105),求有多少个 i[l,r] 满足 (ai+x)modm<(ai+y)modm

题解#

先考虑转化一下那个不等式,这一步应该不是很难,我的方法是,肯定先把 x,y 都对 m 取模,然后钦定 x<y,然后分别以 xy 为断点画出两条以 modm 的余数环断开形成的链。此时容易发现,记 ai=aimodm,则有当 ai[0,my)[mx,m) 时,i 满足条件。x>y 的情况交换 xy,然后用区间长度减去算出的结果就是答案;x=y 的情况显然无解。

于是问题转化为每次给定一个下标范围 [l,r] 和一个值域范围 [x,y] 以及 m,求 [l,r] 中有多少数对 m 取模后的值在范围 [x,y] 中。

这个计数问题的答案显然具有可差分性,所以我们每次在 r 的地方存下这个询问,并给它赋一个正号(+),再在 l1 的地方存下这个询问,并给它赋一个负号(),然后我们只需要用这种方式把询问离线下来从 1n 扫一遍,中途遇到挂了询问的地方就计算 [1,i] 的答案,然后根据 +/ 把贡献丢给对应的询问就行了。

然后考虑怎么维护这个计数。其实挺简单。

取模。直接上根号分治。设值域上限为 V

mV 时,我们直接开个桶 cnti,j 表示 modi 结果为 j 的数有多少个,每次 O(V) 插入,O(V) 查询即可,则此部分复杂度为 O(nV+qV)

m>V 时,我们每次把目标值域范围暴力地往上跳,每跳一次上下界同时 +m,所以最多跳 O(V) 到顶就可以结束了。于是我们每次要回答有多少个 i 满足 ai[l,r],值域分块即可。因为总共有 O(qV) 次查询和 O(n) 次插入,所以得写一个 O(V) 插入,O(1) 查询的值域分块来平衡复杂度,每次记录块内前缀和、块内后缀和以及块间前缀和即可。此部分复杂度为 O(nV+qV)

总时间复杂度为 O(nV+qV),只要不是实现得太烂的都能过。

Code:#

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define re register
const int N=1e5+113,M=5e5+113,SQ=370;
int n,m,a[N],L=1,R,cnt[N],ans[M],c[N];
int s[SQ][SQ];
int T,idv[N],vl[N],vr[N],vmax,pre[N],suf[N],sv[N];
struct query{
    int f,x,y,mod,id;
};
vector<query>v[N];
#define pb push_back
il int read(){
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
il void init(){
    for(re int i=1;i<=vmax;i++)idv[i]=(i-1)/T+1;
    for(re int i=1;i<=idv[vmax];i++)
        vl[i]=vr[i-1]+1,vr[i]=i*T;
    vr[idv[vmax]]=vmax;
}
il int Ask(int x,int y,int mod){
    if(x>y)return 0;
    int res=0;
    for(re int i=x;i<=y;i++)res+=s[mod][i];
    return res;
}
il int solve_le(int l,int r,int x,int y,int mod){
    if(mod==1)return 0;
    if(x==y)return 0;
    bool flg=0;
    if(x>y)swap(x,y),flg=1;
    int res=Ask(mod-x,mod-1,mod)+Ask(0,mod-y-1,mod);
    return flg?r-l+1-res:res;
}
il int GetCntV(int l,int r){
    if(idv[l]==idv[r])return (l==vl[idv[l]])?pre[r]:pre[r]-pre[l-1];
    return suf[l]+pre[r]+sv[idv[r]-1]-sv[idv[l]];
}
il int solve_gt(int l,int r,int x,int y,int mod){
    if(mod==1)return 0;
    if(x==y)return 0;
    bool flg=0;
    if(x>y)swap(x,y),flg=1;
    int res=0,now=mod;
    if(x){
        int l1=mod-x,r1=mod-1;
        while(1){
            res+=GetCntV(l1,r1);
            if(r1==vmax)break;
            if(l1+mod>vmax)break;
            l1+=mod,r1+=mod;
            r1=min(r1,vmax);
        }
    }
    int l2=0,r2=mod-y-1;
    while(1){
        res+=GetCntV(l2,r2);
        if(r2==vmax)break;
        if(l2+mod>vmax)break;
        l2+=mod,r2+=mod;
        r2=min(r2,vmax);
    }
    return flg?r-l+1-res:res;
}
il void Add(int x){
    for(re int i=1;i<=T;i++)s[i][x%i]++;
    for(re int i=x;i<=vr[idv[x]];i++)pre[i]++;
    for(re int i=vl[idv[x]];i<=x;i++)suf[i]++;
    for(re int i=idv[x];i<=idv[vmax];i++)sv[i]++;
}
int main(){
    n=read(),m=read();
    for(re int i=1;i<=n;i++)a[i]=read(),vmax=max(vmax,a[i]);
    T=sqrt(vmax);
    for(re int i=1;i<=m;i++){
        int l=read(),r=read(),x=read(),y=read(),mod=read();
        x%=mod,y%=mod;
        if(l>1)v[l-1].pb({-1,x,y,mod,i});
        v[r].pb({1,x,y,mod,i});
    }
    init();
    for(re int i=1;i<=n;i++){
        Add(a[i]);
        for(re query j:v[i]){
            if(j.mod>T)ans[j.id]+=j.f*solve_gt(1,i,j.x,j.y,j.mod);
            else ans[j.id]+=j.f*solve_le(1,i,j.x,j.y,j.mod);
        }
    }
    for(re int i=1;i<=m;i++)cout<<ans[i]<<'\n';
    return 0;
}
posted @   MrcFrst  阅读(56)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示
主题色彩