题意:给定一个长度为n的数组,求其连所有续子串的GCD一共有几种。
思路:一开始想的是双重循环按照子串的开头的数字枚举子串并求其GCD,在去重。但是这样做先不计算GCD的时间复杂度,光模拟子串的过程就是(n+1)*n/2,而n最大为5*10^5,限制时间6秒,一定会超时。
后来转变思路,改用枚举以每个数字做结尾情况下的子串的GCD,这样,在计算下一个数字做结尾的子串的GCD的时候只需用到上个上个数字做结尾情况下的去重的GCD即可。这样需要对每种情况下的GCD做一定的处理:
例如:9 6 2 4这4个数字
以9结尾:9——>9 9结尾的情况下,GCD只有9一种情况。
以6结尾:9 6——>3
6——>6 6结尾的情况下,GCD有3、6两种情况。
以2结尾:相当于在上个以6结尾的子串后面在添上一个6即可
9 6 2——>1
6 2——>2
2 ——>2 2结尾的情况下GCD有1、2两种情况。
以4结尾:9 6 2 4——>1(相当于GCD(9,6,2)=1在于4取GCD)
6 2 4 == GCD(6,2)=2 4——>2
4——>4 4结尾的情况下GCD有1、2、4
最后将每个数字做结尾的子串得到的GCD放到数组ans中用unique()去重。(去重前要ans排序)
需要开辟一个存放每个数字最结尾的子串得到的GCD的数组temp,ai最大10^18,数组开到18lg10就够用。在循环中的长度相当于一个常数,外层循环取结尾数字时的循环长度为n,算上GCD时间复杂度为lg2,这样总体的时间复杂度n*18lg10*lg2,相当于常数*n==n。就不会超时啦~
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
#define LL long long
using namespace std;
LL GCD (LL a,LL b)
{
return b==0?a:GCD(b,a%b);
}
LL num[500005],ans[500005];;
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%I64d",&num[i]);
LL temp[n+1];
int cnt=1,sum=0,ends=0;
LL t;
for(int i=0;i<n;i++)
{
for(int j=0;j<ends;j++){
t=GCD(num[i],temp[j]);
if(temp[j]!=t)
{
ans[sum++]=temp[j];
temp[j]=t;
///cnt++;
}
}
temp[ends++]=num[i];///将上面for循环得到的GCD情况进行记录,含重复
sort(temp,temp+ends);
ends=unique(temp,temp+ends)-temp;///对每种数字结尾的子串的GCD情况进行去重
///ends=cnt;
///cnt=1;
}
for(int i=0;i<ends;i++)///不要忘记退出循环后,以a[n-1]结尾的GCD还要进行统计。
ans[sum++]=temp[i];
sort(ans,ans+sum);
int x=unique(ans,ans+sum)-ans;
printf("%d\n",x);
return 0;
}