http://acm.hdu.edu.cn/showproblem.php?pid=5726
题目大意:
给你一串数,然后在给你多个区间让你求这个区间内的数的gcd
以及这整串数和这个区间gcd相等的子序列有多少个。
第一个问题求gcd很容易的吶
long long gcd(long long a,long long b)
{
return a%b?gcd(b,a%b):b;
}
第二个问题需要求区间可以选择线段树。
这里安利一个求区间的算法RMQ算法
因为gcd是不增数列所以可以用到这个方法
先设A[]为题目给出的数串用样例1为代表
A[]={1,2,4,6,7};
然后我们设 a[i][j]数组为区间i到2^j之间的数的gcd值
所以
a[1][0]=1;a[1][1]=gcd(1,2);a[1][2]=gcd(1,2,4,6);
a[2][0]=2;a[2][1]=gcd(2,4);a[3][1]=gcd(4,6)
可以看出
a[2][1]=gcd(a[2][0],a[3][0]);
a[1][2]=gcd(a[1][1],a[3][1]);
从上可以推出状态转移方程
void rmq(long long num)
{
for(int j=0;j<30;j++)
for(int i=1;i<=num&&i+(1<<j)-1<=num;i++)
{
if(j==0)
ans[i][j]=a[i];
else ans[i][j]=gcd(ans[i][j-1],ans[i+(1<<j-1)][j-1]);//状态转移方程
}
}
最后结果进行二分就可以了
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
#include<map>
#include<math.h>
#define M 100006
using namespace std;
long long ans[M][40];
long long a[M];
map<int,long long>q;
long long gcd(long long a,long long b)
{
return a%b?gcd(b,a%b):b;
}
void rmq(long long num)
{
for(int j=0;j<30;j++)
for(int i=1;i<=num&&i+(1<<j)-1<=num;i++)
{
if(j==0)
ans[i][j]=a[i];
else ans[i][j]=gcd(ans[i][j-1],ans[i+(1<<j-1)][j-1]);//状态转移方程
}
}
int find(int L,int R) //查找区间 区间长度为R-L+1
{
int k=log2(R-L+1);
return gcd(ans[L][k],ans[R-(1<<k)+1][k]);
}
int main()
{
int T;
int cas=0;
scanf("%d",&T);
while(T--)
{
printf("Case #%d:\n",++cas);
memset(ans,0,sizeof(ans));
long long g[M];
long long n;
scanf("%lld",&n);
int i,j,k;
for(i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
rmq(n);
int m;
scanf("%d",&m);
for(i=1;i<=n;i++) //固定初始点
{
int st=i;
while(st<=n)
{
int t=0;
int head=st;
int ed=n;
int v=find(i,st);
while(head<=ed) //二分结果
{
int mid=(head+ed)/2;
int now=find(i,mid);
if(now==v)
{
t=mid;
head=mid+1;
}
else if(now<v)
{
ed=mid-1;
}
else
{
head=mid+1;
}
}
q[v]+=t-st+1;
st=ed+1;
}
}
while(m--)
{
int aa,b;
scanf("%d%d",&aa,&b);
int k=find(aa,b);
printf("%d %lld\n",k,q[k]);
}
q.clear();
}
}