题意:给定一个长度为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; }