思路
众所周知,对于两个数 x , y x,y x,y,满足 gcd ( x , y ) \gcd(x,y) gcd(x,y) 要么等于 x x x 中的一个,要么小于等于 x 2 \frac x 2 2x。
证明是简单的,我们分情况讨论:
- x x x 是 y y y 的倍数: gcd ( x , y ) = x \gcd(x,y)=x gcd(x,y)=x;
- 否则, gcd ( x , y ) \gcd(x,y) gcd(x,y) 是 x x x 的因数,而 x x x 的因数最大也是 x 2 \frac x 2 2x。
所以,我们去维护每个 i i i 为起点的区间,每一次找一段最大公约数相同的区间,这样的区间最多 log n \log n logn 个。
如何去找一段最大公约数相同的区间呢?
考虑用 ST 表,这样就能 O ( 1 ) O(1) O(1) 求出任意一段区间的最大公约数,然后二分这段区间的长度即可。
最后,把二分出的长度加入对应的最大公约数的贡献(具体可以用 map),输出即可。
时间复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n),足以通过本题。
代码
#include <bits/stdc++.h>
using namespace std;
template<typename T> inline void read(T &x)
{
x = 0;
T f = 1;char ch = getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')
{
f = -1,ch = getchar();
break;
}
ch = getchar();
}
while(ch>='0'&&ch<='9')
x = (x<<3)+(x<<1)+ch-48,ch = getchar();
x*=f;
}
template<typename T> inline T read()
{
T x;read(x);return x;
}
template<typename T> void write(T x)
{
if(x<0) x = -x,putchar('-');
if(x>9) write(x/10);
putchar(x%10+48);
}
template<typename T> inline void writen(T x)
{
write(x);
putchar(10);
}
const int N = 1e5+5;
int n,m,a[N],st[N][18];
unordered_map<int,long long> mp;
int ask(int l,int r)
{
int k = log2(r-l+1);
return __gcd(st[l][k],st[r-(1<<k)+1][k]);
}
signed main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(n),read(m);
for(int i = 1;i<=n;i++)
read(a[i]),st[i][0] = a[i];
for(int j = 1;j<=17;j++)
for(int i = 1;i+(1<<j)-1<=n;i++)
st[i][j] = __gcd(st[i][j-1],st[i+(1<<(j-1))][j-1]);
for(int i = 1;i<=n;i++)
{
int p = i,val = a[i];
while(p<=n)
{
int l = 0,r = n-p,res = 0;
while(l<=r)
{
int mid = (l+r)/2;
if(__gcd(val,ask(p,p+mid))==val) l = mid+1,res = mid;
else r = mid-1;
}
mp[val]+=res+1;
p+=res+1;
val = __gcd(val,a[p]);
}
}
while(m--) writen(mp[read<int>()]);
return 0;
}