1)设dp[ i ][ j ]为从第i个数开始,共1<<j个数的gcd;即dp[1][0]=a1 , dp[1][2]=gcd(a1,a2,a3,a4);
求的时候可以将其二分,先求gcd(a1,a2),再求gcd(a3,a4),再把它们两的值gcd。
2)所以得到转移方程 : dp[ i ][ j ] = gcd( dp[ i ][ j-1 ] , dp[(1<<( j -1))+i ][ j-1 ] );
1<<(j-1) 是1<<j 的一半,所以 i + 1<<(j-1) 可以 使 i 到达中点位置。
比如1,2,3,4,5,6,7,8。dp[1][3]=gcd( dp[1][2] , dp[5][2])
3)前面两步都是为了第三步,O(1)直接得出区间 [ l , r ] 的gcd值。具体这样做:
k=log2(r-l+1) ;gcd( l , r) = gcd ( dp[ l ][ k ] ,dp[r-(1<<k)+1][ k ]);
r-l+1是区间的长度,k=log2(r-l+1) ,但是1<<k不能保证正好等于(r-l+1),如果仅仅gcd( l , r) = dp[ l ][ k ]的话可能会漏掉后面的几个数字。好的办法在gcd一个 dp[t][k],使得t+1<<k = r。
综上:经过预处理之后我们可以O(1)求得区间 [ l , r ] 的gcd值。
4)预处理出所有区间gcd值出现的次数
发现固定 l,r增加,gcd的值是降序的。换句话说,gcd的值是有序的,而且每次gcd变化至少的 减少2倍的(因为最小的质因子是2),所以所有gcd的值的数量不会超过log(1e9)。因为有序,想到二分,枚举所有的左边界,二分其右边界,假设当前[ l , i]的gcd是 g,二分就是要找到第一个比g小的位置l。那么i到l之间的数gcd肯定也都是g。然后用map记录好每个gcd值出现的次数。
/* ***********************************************
Author :angon
************************************************ */
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
#define REP(i,k,n) for(int i=k;i<n;i++)
#define REPP(i,k,n) for(int i=k;i<=n;i++)
#define scan(d) scanf("%d",&d)
#define scann(n,m) scanf("%d%d",&n,&m)
#define mst(a,k) memset(a,k,sizeof(a));
#define LL long long
#define maxn 100005
#define mod 100000007
/*
inline int read()
{
int s=0;
char ch=getchar();
for(; ch<'0'||ch>'9'; ch=getchar());
for(; ch>='0'&&ch<='9'; ch=getchar())s=s*10+ch-'0';
return s;
}
inline void print(int x)
{
if(!x)return;
print(x/10);
putchar(x%10+'0');
}
*/
int gcd(int a,int b)
{
return b?gcd(b,a%b):a;
}
map<int,LL>cnt;
int a[maxn],dp[maxn][18],n;
void init()
{
REPP(i,1,n) dp[i][0]=a[i];
REPP(j,1,17)
{
REPP(i,1,n)
{
if((1<<j)+i-1<= n)
dp[i][j] = gcd( dp[i][j-1] ,dp[(1<<(j-1))+i][j-1]);
}
}
}
int find(int l,int r)
{
int k=log2(r-l+1);
return gcd(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
int t,cas=1,q;
scan(t);
while(t--)
{
cnt.clear();
scan(n);
REPP(i,1,n) scan(a[i]);
init();
REPP(i,1,n)
{
int j=i,g=a[i];
while(j<=n)
{
int l=j,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(find(l,r)==g) l=mid+1;
else r=mid-1;
}
cnt[g] += l-j+1;
j=l+1;
g=gcd(g,a[j]);
}
}
scan(q);
printf("Case #%d:\n",cas++);
while(q--)
{
int l,r;
scann(l,r);
int ans=find(l,r);
printf("%d %I64d\n",ans,cnt[ans]);
}
}
return 0;
}