【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;
}

 

 

posted @ 2017-09-21 20:15  GXZlegend  阅读(575)  评论(0编辑  收藏  举报