GCD
题目链接
题目大意
给你一个区间,先让你求这个区间的GCD值,再让你求有多少个区间的GCD值和它相等。
题解
ST表+二分
首先可以看到题目要求的是一段区间的GCD值,而GCD这个值我们可以通过区间来维护,而且GCD的值是逐渐递减且有限的,所以我们可以用ST表维护每一段区间的GCD值,然后又因为GCD是递减的,对于某个GCD的值,我们可以对于每个左端点二分找到一个最大的右端点,使得这段的GCD与它相等,这样对于每一项二分之后用右端点减去左端点就可以算出这一段GCD的个数,用MAP维护下就可以了。
一开始用线段树结果超时了…
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <map>
#define maxn 100005
#define LL long long
using namespace std;
int d[maxn][50];
int a[maxn],T,n,q;
map<int,LL> mp;
int gcd(int a,int b)
{
if (b==0) return a;
else return gcd(b,a%b);
}
void setup(int d[][50],int n)
{
for (int i=0;i<=n;i++) d[i][0]=a[i];
for (int j=1;(1<<j)<=n;j++)
for (int i=1; i+(1<<j)-1<=n ;i++) d[i][j]=gcd(d[i][j-1],d[i+ (1<<(j-1))][j-1]);
}
int rmq(int l,int r)
{
int k=0;
while (1<<(k+1) <=r-l+1) k++;
return gcd(d[l][k],d[r-(1<<k)+1][k]);
}
int main()
{
int Case=1;
scanf("%d",&T);
while (T--)
{
memset(d,0,sizeof(d));
mp.clear();
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
setup(d,n);
for (int i=1;i<=n;i++)
{
int g=a[i],l=i,r=n,mid,R=0,L=i,t=0;
while (R<=n)
{
while (l!=r)
{
mid=(l+r)>>1;
t=rmq(i,mid);
if (g<=t) l=mid+1;
else r=mid;
}
t=rmq(i,r);
if (g!=t) { r--; l--; }
mp[g]+=(r-L+1);
g=gcd(g,a[r+1]);
L=l+1;
R=++r;
r=n;
}
}
scanf("%d",&q);
printf("Case #%d:\n",Case++);
while (q--)
{
int a,b;
scanf("%d%d",&a,&b);
int c=rmq(a,b);
printf("%d %I64d\n",c,mp[c]);
}
}
return 0;
}