题意:
给出一串n个数的序列,然后给q个询问,每个询问查询在下标区间[l,r]的数的gcd是多少,然后问这n个数中有多少个区间的gcd等于这个询问的gcd。
思路:
求解gcd,没有修改操作,可以想到用RMQ,可以O(1)查询。
剩下的是怎么求每个gcd对应有多少个区间。从左向右枚举左端点,那么可以发现左端点l固定的情况下,gcd会随着向右而不递增。这就有了二分的性质,我们二分出每个gcd最远的位置now,说明从last+1到now这段区间内的每个点当作右端点r,都可以和令[l,r]这段区间的gcd都相同。直接map[gcd] += now - last + 1算贡献即可。
还有一点很关键的是对于每个gcd都要二分一次,那么在固定左端点的情况下,一共会有多少个gcd呢,因为gcd每次变化至少是除以2,所以最多只有logn次,故该算法复杂度是O(nlognlogn)。
代码:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
struct Query {
int l, r;
} que[MAXN];
int dp[MAXN][20], a[MAXN];
int gcd(int a, int b) {
return !b ? a : gcd(b, a % b);
}
void RMQ_init(int n) {
for (int i = 0; i < n; i++) dp[i][0] = a[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 0; i + (1 << j) - 1 < n; i++)
dp[i][j] = gcd(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
int RMQ(int l, int r) {
int k = 0;
while ((1 << (k + 1)) <= r - l + 1) ++k;
return gcd(dp[l][k], dp[r - (1 << k) + 1][k]);
}
int ans[MAXN];
int main() {
//freopen("in.txt", "r", stdin);
int T, cs = 0;
scanf("%d", &T);
while (T--) {
int n, q;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
RMQ_init(n);
scanf("%d", &q);
for (int i = 1; i <= q; i++) {
scanf("%d%d", &que[i].l, &que[i].r);
que[i].l--; que[i].r--;
ans[i] = RMQ(que[i].l, que[i].r);
}
map <int, long long> cnt;
for (int i = 0; i < n; i++) {
int j = i;
while (j < n) {
int g = RMQ(i, j);
int l = i, r = n - 1;
while (l < r) {
int m = (l + r + 1) >> 1;
if (RMQ(i, m) >= g) l = m;
else r = m - 1;
}
cnt[g] += l - j + 1;
j = l + 1;
}
}
printf("Case #%d:\n", ++cs);
for (int i = 1; i <= q; i++)
printf("%d %I64d\n", ans[i], cnt[ans[i]]);
}
return 0;
}