题目
题目传送门
给出一个长度为
n
(
1
<
=
n
<
=
1
0
5
)
n(1<=n<=10^{5})
n(1<=n<=105)的序列和
q
(
1
<
=
q
<
=
3
∗
1
0
5
)
q(1<=q<=3*10^{5})
q(1<=q<=3∗105)个询问,每个询问输出一行,询问
g
c
d
(
a
l
,
a
l
+
1
,
⋯
 
,
a
r
)
=
x
gcd(a_l,a_{l+1},\cdots,a_r)=x
gcd(al,al+1,⋯,ar)=x的
(
i
,
j
)
(i,j)
(i,j)的对数.
题解
- 大佬给我们讲分治时用的例题,我这种不会分治的cb赶紧去学了一下,发现这道题真的非常妙 q w q qwq qwq
- 首先如果我们固定一个左端点,那么在右端点移动的过程中
gcd
\gcd
gcd一定是单调不增的,而如果
gcd
\gcd
gcd出现变化时,它一定至少变了
2
2
2,所以
gcd
\gcd
gcd的变化是
log
\log
log级别的,那么这样我们就可以预处理出每个区间的
gcd
\gcd
gcd。
ST表好啊蒟蒻的ST表讲解qwq - 预处理出每个 gcd \gcd gcd之后,我们就可以以 1 1 1~ n n n的每个点为左端点,二分查找最短的与左端点 gcd \gcd gcd相同的右端点,用 m a p map map记录此段区间个数,一直处理到 R = n R=n R=n;这样我们就能得出题目要求的区间了。
c o d e code code
#include <bits/stdc++.h>
using namespace std;
#define maxn 101000
typedef long long LL;
template <typename T>
inline void read(T &s) {
s = 0;
T w = 1, ch = getchar();
while (!isdigit(ch)) { if (ch == '-') w = -1; ch = getchar(); }
while (isdigit(ch)) { s = (s << 1) + (s << 3) + (ch ^ 48); ch = getchar(); }
s *= w;
}
int n, m;
int a[maxn], lg[maxn], q[maxn];
int f[maxn][25];
map <int, LL> ans;
inline int gcd(int x, int y) { return y ? gcd(y, x % y) : x; }
inline void pre_work() { // 预处理gcd
lg[0] = -1;
for (int i = 1; i <= n; ++i) {
lg[i] = lg[i >> 1] + 1, f[i][0] = a[i];
}
for (int j = 1; j <= 20; ++j) {
for (int i = 1; i <= n - (1 << (j - 1)) + 1; ++i) {
f[i][j] = gcd(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
}
}
}
inline int query(int l, int r) { // 查询
int t = lg[r - l + 1];
return gcd(f[l][t], f[r - (1 << t) + 1][t]);
}
void solve(int x) { // x 为固定的左端点
int L = x, R = x;
while (R <= n) {
int left = L, right = n;
int g = query(x, L);
while (left <= right) {
int mid = (left + right) >> 1;
if (query(x, mid) == g) left = mid + 1;
else right = mid - 1;
}
R = left;
ans[g] += (LL)R - L;
L = R;
}
}
int main() {
read(n);
for (int i = 1; i <= n; ++i) read(a[i]);
pre_work();
for (int i = 1; i <= n; ++i) solve(i); // 处理左端点
read(m);
for (int i = 1; i <= m; ++i) {
int x; read(x);
printf("%lld\n", ans[x]);
}
return 0;
}