题意
给出n(n<=1e5)个数(a[i]<=1e9),q(q <= 1e5)个查询,对于每个查询(l,r),输出a[l]到a[r]之间的数的gcd ansa,以及原数组中gcd等于ansa的区间个数。
思路
对于第一种查询,可以使用st表在O(1)的时间内给出答案。对于第二种查询,考虑以i为左端点的区间,其gcd一定随着j的增大而递减。更准确的描述是呈阶梯状下降的。因此可以采用二分的思想,找到每个水平段的长度,并将此段的gcd值对应的区间数量加上此水平段的长度(可以使用map存储每个gcd值对应的答案)。得到数组后做预处理,从1~n枚举左端点i,算法总体时间复杂度(nlognlogn)。
代码
#include <cstdio>
#include <map>
using namespace std;
typedef long long ll;
const int maxn = 2e5;
const int maxlog = 20;
int n;
int s[maxn];
int dp[maxn][maxlog];
int gcd(int a,int b)
{
if(b == 0) return a;
return gcd(b,a%b);
}
map<int,ll> mp;
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 main()
{
int t;
scanf("%d",&t);
int ks = 1;
while(t--)
{
mp.clear();
scanf("%d",&n);
for(int i = 0; i < n ;i++)
scanf("%d",s+i);
for(int i = 0; i < n; i++)
dp[i][0] = s[i];
for(int j = 1 ; j < maxlog; j++)
for(int i = 0;i < n; i++)
{
dp[i][j] = dp[i][j-1];
if(i+(1<<(j-1)) < n)
dp[i][j] = gcd(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
}
for(int i = 0; i < n; i++)
{
int l = i-1,r = n;
while(l+1 < n)
{
int st = rmq(i,l+1);
int pre = l;
while(l+1 < r)
{
int mid = (l+r)/2;
int gg = rmq(i,mid);
if(gg >= st)
l = mid;
else
r = mid;
}
ll cnt = l - pre;
if(mp.find(st) == mp.end())
mp[st] = 0;
mp[st] += cnt;
r = n;
}
}
printf("Case #%d:\n",ks);
ks++;
int q;
scanf("%d",&q);
for(int i = 0; i <q; i++)
{
int l,r;
scanf("%d%d",&l,&r);
l--,r--;
int sg = rmq(l,r);
printf("%d %lld\n",sg,mp[sg]);
}
}
return 0;
}