前言:

在本章会介绍:

1.快速求gcd(大概log(b)?)

2.gcd和lcm的一些性质

3.基于预处理的gcd求法。(O(1)-O(值域))

补充:stein算法(求gcd)

一.快速求gcd

这里介绍欧几里得求gcd,即辗转相除法。



int gcd(int a,int b)
{
return (b==0)?a:gcd(b,a%b);
}


 在这里就直接上代码了。

二.gcd和lcm的一些性质


 1.不超过正整数n的两个正整数a,b的最大的最小公倍数为n*(n-1),当然n=1的时候要特判。(话说这个性质真废)


 2.gcd(a,b)*lcm(a,b)=a*b。(这个就比较精髓了)


 3.如果知道gcd(a*k,b*k),那么最好求k*gcd(a,b),毕竟中间的mod时很慢的。


4.gcd(a,b)>=gcd(a,b,c)>=gcd(a,b,c,d)。这里推荐一道题。(1414又是毕业季)


5.唤醒一下gcd的“初心”,指的是所有数字的最大的约数。(快去做做1414又是毕业季)


三.基于预处理的gcd求法(Luogu模板5435)


首先我们发现如果每一次求gcd,这不就太慢了,毕竟中间mod来mod去的。这时候介绍一种新的做法。


预处理值域(v)的每一个数,将这个数t分为a,b,c(a,b,c<=sqrt(v) && a*b*c=t),或这个数为质数p,就分为(1,1,p)。这样子我们求gcd(g,h)无非是求h和a,b,c的gcd,再乘起来,而因为一定存在合理分解(a,b,c<=sqrt(v)),所以我们预处理gcd[a,b](a,b<=sqrt(v)),这样预处理就是O(V)查询就是O(1)了。


那么该怎么分解呢?如果是合数a,那么找出它的最小质因数p和a/p的分解(p1,p2,p3),这样a的分解就是(p1*p,p2,p3),再进行重新排序即可。而且一定有这种分法(此处证明则省略)。


下面给出代码:




#include<bits/stdc++.h>
using namespace std;
const int mod=998244353,maxn=5005,v=1000001,radio=1001;
int a[maxn+10],b[maxn+10],n,ans;
int np[v+10],pri[v+10],cnt;
int k[v+10][3];
int _gcd[radio+10][radio+10];
inline int gcd(int a,int b)//O(1)求出gcd
{
int g=1;
for(int tmp,i=0;i<3;i++)
{
if(k[a][i]>radio)//i为质数时,k[][]才会可能大于radio=sqrt(v)
{
if(b%k[a][i]==0) tmp=k[a][i];
else tmp=1;
}
else tmp=_gcd[k[a][i]][b%k[a][i]];
b/=tmp;
g*=tmp;
}
return g;
}
int main()
{
k[1][0]=k[1][1]=k[1][2]=1;
np[1]=1;
for(int i=2;i<=v;i++)
{
if(!np[i]) //质数
{
pri[++cnt]=i;
k[i][2]=i;
k[i][0]=k[i][1]=1;
}
for(int j=1;pri[j]*i<=v;j++)//i的每一个质数倍数
{
np[i*pri[j]]=1;
int *tmp=k[i*pri[j]];
tmp[0]=k[i][0]*pri[j];
tmp[1]=k[i][1];
tmp[2]=k[i][2];
if(tmp[1]<tmp[0]) swap(tmp[1],tmp[0]);
if(tmp[2]<tmp[1]) swap(tmp[2],tmp[1]);
if(i%p[j]==0) break;//如果i是p[j]的倍数,那么就停止,因为后面的数都是i的倍数,而我们要保证每个数分解是先找出最小的质因数
}
}//预处理
for(int i=1;i<=radio;i++) _gcd[i][0]=_gcd[0][i]=i;
for(int _max=1;_max<=radio;_max++)
for(int i=1;i<=_max;i++)
_gcd[i][_max]=_gcd[_max][i]=_gcd[_max%i][i];//递推预处理
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
for(int i=1;i<=n;i++)
{
int now=1,ans=0;
for(int j=1;j<=n;j++)
{
now=1ll*now*i%mod;
ans=(ans+1ll*gcd(a[i],b[j])*now)%mod;
}
cout<<ans<<endl;
}
return 0;
}


再结合代码理解一下即可。

话说这个算法应该是在知道值域范围且求gcd次数过多的情况下使用,但感觉这种情况不太常见。

四.stein算法

辗转相除法在数小的时候优势大,但是数字大的时候,多次的高精度除法取模确实很慢,这里介绍stein算法:

算法步骤:

1.如果a,b皆为偶数,则一起除2,直至一个或两个数为奇数。

2.如果a.b中一个为奇数,那么将偶数一直除2直至为奇数。

3.如果a.b皆为奇数,那么直接用辗转相除法或辗转相减法求gcd即可。

这个算法的优势在于在数字大的时候会用到位运算,从而加快速度。

讲实话这个算法如果不嫌麻烦可以写的,但其实一般情况下辗转相除法是可以的。

下面给出代码:



int stein(int a,int b){
if(a<b) a^=b,b^=a,a^=b; //交换,使a为较大数;
if(b==0)return a; //当相减为零,即两数相等时,gcd=a;
if((!(a&1))&&(!(b&1))) return stein(a>>1,b>>1)<<1; //s1,注意最后的左移,在递归返回过程中将2因子乘上;
else if((a&1)&&(!(b&1)))return stein(a,b>>1); //s2;
else if((!(a&1))&&(b&1))return stein(a>>1,b);
else return stein(a-b,b); //s3;
}


这里给出的是辗转相减法,辗转相除也是一样的。

总结:

GCD和LCM还真有些我们需要去发掘的性质。(又在水博客)