题意:
给含有n个数的序列,现在询问[l,r]的gcd是多少,同时还要求与gcd相同的区间有多少个;
思路:
静态区间可以用rmq,先预处理就可以O(1)的回答询问了;还有就是询问区间的个数,可以先预处理出所有的gcd,用map存下来,预处理的时候枚举左端点,由于gcd是单调递减的,所以可以分层后二分右端点,找到每层的长度,加到map中,由于一个数的因子个数不超过log(n)所以层数比较少才可以这样处理.
#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
#include<string>
#include<map>
#define INF 99999999
#define LL long long
#define maxn 100005
using namespace std;
int n,T,q,l,r;
int a[maxn];
int qq[maxn][2];
int f[maxn][20];//dp数组,存储以第i个数开始,到第i+(1<<j)-1个数之间区间的gcd
map<int,LL>mm;//记录某个gcd的值出现的次数
int gcd(int a,int b)
{
if(b==0)
return a;
return gcd(b,a%b);
}
void init()
{
for(int i=1;i<=n;i++)
{
f[i][0]=a[i];
}
for(int j=1;j<=20;j++)//注意这里j从1开始,因为前面已经初始化了放f[i][0]
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
f[i][j]=gcd(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
int query(int l,int r)
{
if(l>r)
swap(l,r);
int k=(int)(log((double)(r-l+1))/log(2.0));
return gcd(f[l][k],f[r-(1<<k)+1][k]);
}
void solve()//固定每个左端点,二分遍历右端点
{
int l,r,mid;
for(int i=1;i<=n;i++)//枚举每个左端点
{
int val=a[i];
int pos=i;
while(pos<=n)//由于gcd是单调递减的,所以可以分层后二分右端点,由于一个数的因子个数不超过log(n)所以层数比较少才可以这样处理
{// 2 4 6 9 12 18 28 40 这组数据 2 4 6 为一层,过了这一层pos变为4 (每一层的gcd不同)
val=query(i,pos);
l=pos;r=n;//确定二分遍历右端点的区间
while(l<=r)
{
mid=(l+r)/2;
if(query(i,mid)==val)
l=mid+1;
else
r=mid-1;
}
mm[val]+=(r-pos+1);
pos=l;
}
}
}
int main()
{
scanf("%d",&T);
int cnt=1;
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
init();
mm.clear();
solve();
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%d %d",&l,&r);
qq[i][0]=l;
qq[i][1]=r;
}
printf("Case #%d:\n",cnt++);
for(int i=1;i<=q;i++)
{
printf("%d %lld\n",query(qq[i][0],qq[i][1]),mm[query(qq[i][0],qq[i][1])]);
}
}
return 0;
}