题目描述
难度分:2100
输入n(1≤n≤105)和长为n的数组a(1≤a[i]≤109)。
然后输入q(1≤q≤105)和q个询问,每个询问输入两个数L和R,表示下标从L到R的连续子数组(1≤L≤R≤n)。
对于每个询问,输出R−L+1−C,其中C为子数组中的元素x的个数,满足x能整除子数组中的每个数。
输入样例
5
1 3 2 4 2
4
1 5
2 5
3 5
4 5
输出样例
4
4
1
1
算法
数学+ST表
构建一个ST表用于查询区间的最大公约数gcd;构建一个映射val2pos表示“数值→这个数值在数组a中出现的索引列表(升序)”。
对于一个询问[L,R],其中能被区间内所有数整除的整数一定是区间最大公约数g。在val2pos[g]上进行二分查找,确定落在区间[L,R]内的索引有多少个,这些索引的个数就是区间[L,R]内可以整除所有元素的整数个数C。
复杂度分析
时间复杂度
预处理出ST表的时间复杂度为O(nlog2n)。处理每个询问[L,R]时,需要对每个索引列表二分查找,时间复杂度是O(log2n),处理O(q)条询问的时间复杂度为O(qlog2n)。
综上,整个算法的时间复杂度为O((n+q)log2n)。
空间复杂度
映射val2pos中存的也是a的O(n)个索引,所以空间复杂度为O(n)。ST表的空间消耗是O(nlog2n)。
因此,空间消耗的瓶颈在于ST表,整个算法的额外空间复杂度为O(nlog2n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200010, M = 20;
int n, q, a[N], st[N][M];
struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
};
int query(int l, int r) {
int k = log2(r - l + 1);
return __gcd(st[l][k], st[r - (1<<k) + 1][k]);
}
int main() {
int n;
scanf("%d", &n);
unordered_map<int, vector<int>, custom_hash> val2pos;
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
val2pos[a[i]].push_back(i);
}
for(int j = 0; j < M; j++) {
for(int i = 1; i + (1 << j) - 1 <= n; i++) {
if(!j) {
st[i][j] = a[i];
}else {
st[i][j] = __gcd(st[i][j - 1], st[i + (1<<(j - 1))][j - 1]);
}
}
}
scanf("%d", &q);
while(q--) {
int l, r;
scanf("%d%d", &l, &r);
int g = query(l, r);
if(!val2pos.count(g)) {
printf("%d\n", r - l + 1);
}else {
auto&pos = val2pos[g];
int low = lower_bound(pos.begin(), pos.end(), l) - pos.begin();
int high = upper_bound(pos.begin(), pos.end(), r) - pos.begin();
int C = high - low;
printf("%d\n", r - l + 1 - C);
}
}
return 0;
}