题意:
有n个数,询问格式: l r x mod,对于区间[l,r]中的一个位置k,如果k%mod == x,那么就有1的贡献,否则就是0的贡献,求区间的贡献和。
思路:
容易发现当mod很大的时候需要计算的点就会越来越稀疏,从数据的范围来看只需要进行常数级别的优化就可以了。总体思想是当mod很小时,直接暴力枚举,当mod比较大时则每次步长改为mod,初始的位置为x。利用前缀和可以进一步优化,先记录所有的询问,假如询问区间[l,r],在前缀和中只需要sum[r]-sum[l-1]即可。利用这个原理,给每个询问打上标记,l-1的位置插入一个-1的点,r的位置插入一个1的点。再用一个二维数组 p [ i ] [ j ] p[i][j] p[i][j]表示mod=i时余数为j的前缀和。然后从左往右枚举位置,对于每个位置都要暴力统计做mod比较小时的情况,加入到前缀和中。然后再枚举在这个位置插入的那些点了,在分下情况就可以解决了。mod怎么算比较小呢,自己算着差不多取个上界就好了。
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int a[N];
struct node{
int x,d,id,p;
node(){}
node(int x,int d,int id,int p){
this->x=x;
this->d=d;
this->id=id;
this->p=p;
}
};
int p[405][405];
int mp[N],ans[N];
vector<node>v[N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int l,r,x,d;
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&l,&r,&x,&d);
v[l-1].push_back(node(x,d,i,-1));//前缀和的思想val[l,r]=sum[r]-sum[l-1]
v[r].push_back(node(x,d,i,1));
}
int mx=400;//小于等于400暴力
for(int i=1;i<=n;i++){
for(int j=1;j<=mx;j++){
p[j][a[i]%j]++;//统计前缀和
}
mp[a[i]]++;//统计a[i]出现的次数,mod>400时计算需要用确切的数字
for(node t:v[i]){
if(t.d<=mx){//mod较小,直接计算
ans[t.id]+=t.p*p[t.d][t.x];
}
else{//mod较大,枚举统计
for(int k=t.x;k<=50000;k+=t.d){
ans[t.id]+=t.p*mp[k];
}
}
}
}
for(int i=1;i<=m;i++){
printf("%d\n",ans[i]);
}
return 0;
}