题意:给n个数,再来q个询问,每次给出l,r 输出gcd(a[l][,a[l+1],........,a[r])和区间最小公约数与该最小公约数相同的区间的个数
求最小公约数gcd用倍增RMQ
定义f[i][k]为从a[i]开始长度为2^k的区间的最小公约数
那么f[i][k]=gcd(f[i][k-1],f[i+(1<<(k-1))][k-1]
举个例子:n=5,a[5]={1,2,4,6,7}
f[i][0]=a[i]; f[1][1]=gcd(a[1],a[2]);
而f[1][0]=a[1];f[2][0]=a[2];
所以f[1][1]=gcd(f[1][0],f[2][0])
所谓的倍增就是把区间不断分成两半达到nlog级别
所以我们通过倍增可以求得gcd();但后面的区间的个数怎么求呢?
gcd(l,r)有这样一个性质,gcd(l,r)>=gcd(l,r+k),此处的gcd(l,r)为区间l到r的最小公约数
因为每多一个数约束就会大一些
所以我们遍历左区间,通过二分查找和上面的这个特性找最小公约数为g的区间的个数,用mp[g]表示个数
上面这句话看不懂没关系,很正常,语言表达能力不行
下面我把思路再讲下,就很容易理解了
我们上面说了要遍历左区间,那么先g1=a[l],
如果gcd(a[l],a[l+1]......,a[l+k])==g1,那么是不是说明最小公约数为g的区间的个数最少有k个,所以mp[g1]+=k
又因为gcd(区间范围)是单调不递增的,那么我们是不是可以用二分找到l+k
那么我们为一个gcd找到了部分的区间数,继续
那么gcd(l,l+k+1) 是不是一定是一个新的gcd,如果不是那二分找到的就是l+k+1了
同理,我们令g2=gcd(l,l+k+1),如果gcd(a[l],a[l+1],...........,a[l+k+1].......a[l+p])==g2,且gcd(a[l],a[l+1],...........,a[l+k+1].......a[l+p+1])!=g2
我们用二分找到l+p后,那么mp[g2]+=l+p-l,//这里不是1是L
直到l+p<=n结束第一个左区间的遍历
这样的一次遍历中gx有多少个呢?g最多有log1000,000,000
因为a[i]最多有个log1000,000,000质因数
继续下一个左区间的遍历。。。。。。。。。。。。。
代码实现:
用cin和cout超时。。。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+7;
int t,n,q,l,r;
map<int,ll> mp;
int a[N];
int f[N][30];
int GCD(int a ,int b)
{
return b==0?a:GCD(b,a%b);
}
void RMQ()
{
int i,k;
for(k=1;k<=20;k++)
for(i=1;i<=n;i++)
if(i + (1 << k)-1<=n)
f[i][k]=GCD(f[i][k-1],f[i+(1<<(k-1))][k-1]);
}
int query(int l,int r)
{
int k=(int)(log(r-l+1.0)/log(2.0));
return GCD(f[l][k], f[r - (1 << k) +1][k]);
}
void pre()
{
int l,r,mid,pos;
for(int i=1;i<=n;i++)
{
pos=i;
while(pos<=n)
{
l=pos; r=n;
int k=query(i,pos);
while(l<r)
{
mid=(l+r+1)/2;
if (query(i,mid)<k) r=mid-1;
else l=mid;
}
mp[k]+=(l-pos+1); pos=l+1;
}
}
}
int main()
{
cin>>t;
for(int ii=1;ii<=t;ii++)
{
mp.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>a[i];
f[i][0]=a[i];
}
RMQ();
pre();
scanf("%d",&q);
printf("Case #%d:\n",ii);
for(int i=0;i<q;i++)
{
scanf("%d%d",&l,&r);
int ans=query(l,r);
printf("%d %lld\n",ans,mp[ans]);
}
}
return 0;
}