题意:给n个数和q次查询,每次查询输入 [l,r] [ l , r ] ,输出在序列中有多少个子区间的gcd值与查询的这个区间相同。
大致可以将题目的主要矛盾分为2点:
1. 快速的查询区间gcd值
2. 由于不能遍历所有子区间,如何尽可能的减少遍历的子区间
解决方法:
1.因为只需要查询,可以简单的用st表实现。
for (int j = 1; j <= 20; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
st[j][i] = __gcd(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
}
}
int query(int l, int r) {
int d = Log[r - l + 1];
return __gcd(st[d][l], st[d][r - (1 << d) + 1]);
}
2.考虑到gcd具有单调性,即固定左端点,随着右端点慢慢往右,gcd的值只会越来越小。可以通过二分找出就目前的gcd来说,右端点最远的位置。
通过这种方法可以找出所有的gcd,但是由于空间的原因,不能把所有的gcd都存下来。我们只需要记录需要查询的gcd出现的次数,用map来维护,不需要查询的就不管它。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
int T, n, q, st[25][maxn], Log[maxn], l[maxn], r[maxn], kase = 0;
int query(int l, int r) {
int d = Log[r - l + 1];
return __gcd(st[d][l], st[d][r - (1 << d) + 1]);
}
map <int, ll> mp;
int main() {
#ifdef __APPLE__
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
Log[2] = 1;
for (int i = 3; i <= 100001; i++) Log[i] = Log[i >> 1] + 1;
cin >> T;
while (T--) {
mp.clear();
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> st[0][i];
}
for (int j = 1; j <= 20; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
st[j][i] = __gcd(st[j - 1][i], st[j - 1][i + (1 << j - 1)]);
}
}
cin >> q;
for (int i = 1; i <= q; i++) {
cin >> l[i] >> r[i];
mp[query(l[i], r[i])] = 0;
}
for (int i = 1; i <= n; i++) {
int j = i;
while (1) {
int gcd = query(i, j);
int left = j, right = n;
int x = 0;
while (left <= right) {
int mid = (left + right) >> 1;
if (query(i, mid) == gcd) {
x = mid;
left = mid + 1;
}
else {
right = mid - 1;
}
}
if (mp.find(gcd) != mp.end()) {
mp[gcd] += x - j + 1;
}
j = x + 1;
if (j > n)
break;
}
}
cout << "Case #" << ++kase << ":" << endl;
for (int i = 1; i <= q; i++) {
int gcd = query(l[i], r[i]);
cout << gcd << " " << mp[gcd] << endl;
}
}
}