【bzoj2506】calc 根号分治+STL-vector+二分+莫队算法
题目描述
给一个长度为n的非负整数序列A1,A2,…,An。现有m个询问,每次询问给出l,r,p,k,问满足l<=i<=r且Ai mod p = k的值i的个数。
输入
第一行两个正整数n和m。
第二行n个数,表示A1,A2,…,An。
以下m行,每行四个数分别表示l,r,p,k。满足1<=l<=r<=n。
输出
对于每个询问,输出一行,表示可行值i的个数。
样例输入
5 2
1 5 2 3 7
1 3 2 1
2 5 3 0
样例输出
2
1
题解
根号分治+STL-vector+二分+莫队算法
对于$p\le \sqrt A$的部分,由于只有$\sqrt A$种模数,因此我们可以开$vector[i][j]$维护模$i$等于$j$的数的出现位置,查询时直接在$vector[p][k]$上二分即可。这个过程预处理时间是$O(n\sqrt A)$,单次查询时间是$O(log n)$。
对于$p>\sqrt A$的部分,考虑到满足$\mod p=k$的数最多只有$\sqrt A$个,因此可以暴力统计这些数中的每一个在序列中的出现次数。由于此时再使用vector+二分的话时间复杂度为$O(n\sqrt A\log n)$,会TLE,因此需要使用莫队算法并使用桶来维护每个数的出现次数。这样极限复杂度为$O(n\sqrt n+n\sqrt A)$。
因此总的最坏时间复杂度为$O(n\sqrt n+n\sqrt A)$。
#include <cmath> #include <cstdio> #include <vector> #include <algorithm> using namespace std; vector<int> v[210][210]; int a[100010] , cnt[10010] , ans[100010] , si , tot; struct data { int l , r , p , k , id; bool operator<(const data &a)const {return (l - 1) / si == (a.l - 1) / si ? r < a.r : l < a.l;} }q[100010]; int main() { int n , m , i , j , w , x , y , z , lp = 1 , rp = 0; scanf("%d%d" , &n , &m) , si = (int)sqrt(n); for(i = 1 ; i <= n ; i ++ ) { scanf("%d" , &a[i]); for(j = 1 ; j <= 100 ; j ++ ) v[j][a[i] % j].push_back(i); } for(i = 1 ; i <= m ; i ++ ) { scanf("%d%d%d%d" , &w , &x , &y , &z); if(y <= 100) ans[i] = upper_bound(v[y][z].begin() , v[y][z].end() , x) - lower_bound(v[y][z].begin() , v[y][z].end() , w); else q[++tot].l = w , q[tot].r = x , q[tot].p = y , q[tot].k = z , q[tot].id = i; } sort(q + 1 , q + tot + 1); for(i = 1 ; i <= tot ; i ++ ) { while(lp > q[i].l) lp -- , cnt[a[lp]] ++ ; while(rp < q[i].r) rp ++ , cnt[a[rp]] ++ ; while(lp < q[i].l) cnt[a[lp]] -- , lp ++ ; while(rp > q[i].r) cnt[a[rp]] -- , rp -- ; for(j = q[i].k ; j <= 10000 ; j += q[i].p) ans[q[i].id] += cnt[j]; } for(i = 1 ; i <= m ; i ++ ) printf("%d\n" , ans[i]); return 0; }