数论学习笔记——最大公约数、乘法逆元
-
一.最大公约数
-
[求最大公约数]
1.求两个数——辗转相除法
原理:gcd(x,y)=gcd(y,x%y)
时间复杂度:O(log n)
code模板
int gcd(int x,int y)
{
return (y==0?x:gcd(y,x%y));
}
2.求多个数
求相邻两个的最大公约数,用这个数再跟下一个继续求,以此类推
3.最小公倍数(lcm)
定理:lcm(a,b)×gcd(a,b)=a×b
code模板
int lcm(int x,int y)
{
return y/gcd(x,y)*x;
}//前提是你得有一个gcd
answer
思路:
有一个很明显的特殊情况,x=y时直接输出1。
根据最小公倍数那里的定理可知,x×y=p×q。同时还要满足gcd(p,q)=x,lcm(p,q)=y;于是我们可以枚举y的因数,用定理求出q(自己推一下可以证明q一定也是y的约数),只要再满足gcd(p,q)=x就行了。
为了减少枚举次数,只枚举<=sqrt(y)的小因子,在一个循环里判断两对即可。时间复杂度:O($ \sqrt{y}$)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,y;
int ans;
ll gcd(ll x,ll y)
{
return y?gcd(y,x%y):x;
}
int main()
{
cin>>x>>y;
if(x==y)//此时p=q=x=y直接输出1即可
{
cout<<1;
return 0;
}
for(ll k=1;k*k<=y;k++)//p,q一定是y的因子,枚举小因子,大因子一除就是
{
if(y%k==0)
{
ll p=k;//小因子
ll q=x*y/k;
if(gcd(p,q)==x)ans++;
//不用判断lcm,q就是用lcm推的
p=y/k;//大因子
q=k*x;//同上
if(gcd(p,q)==x)ans++;
}
}
cout<<ans;
return 0;
}
一个套路,先算两个的,再与第三个继续求。
5.裴蜀定理
a,b不全为0
<1>对任意整数x,y,满足gcd(a,b)|ax+by,且存在整数x,y使得ax+by=gcd(a,b).逆定理也成立
<2>一定存在整数x,y,满足ax+by=gcd(a,b)*n
<3>一定存在整数x\(1\)...x\(i\),满足∑a\(i\)x\(i\)(1~n)=gcd(a\(1\),a\(2\),...,a\(n\)).其逆定理也成立.
- 扩展欧几里得
1.应用:<1>求解不定方程
<2>求解模的逆元
<3>求解线性同余方程
2.求解ax+by=gcd(a,b)的一组整数解(x,y为未知数)
code模板
void exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return;
}
exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-a/b*y;
}//经处理过后的x,y即为合法解
3.求不定方程ax+by=c的一组整数解
讨论:①若gcd(a,b)|c,则有整数解.先用扩展欧几里得求ax+by=gcd(a,b)的解,再乘以c/gcd(a,b),即得原方程特解(x0,y0).
②若gcd(a,b)不整除c,则无整数解.
code模板
#include<bits/stdc++.h>
using namespace std;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0){x=1,y=0;return a;}
int x1,y1,d;
d=exgcd(b,a%b,x1,y1);
x=y1,y=x1-a/b*y1;
return d;//d is gcd(a,b)
}//在求gcd(a,b)过程中求出x,y
int main()
{
int a,b,c,x,y;
cin>>a>>b>>c;
int d=exgcd(a,b,x,y);
if(c%d==0)
printf("%d %d",c/d*x,c/d*y);
else puts("none");
return 0;
}
4.扩展欧几里得求解线性同余方程
问题:给定整数a,b,m,求解同余方程ax≡b%m,如果x存在整数解,则输出任意一个,不存在输出none.
①把同余方程转化为不定方程
由ax≡b(mod m)得ax=m(-y)+b,即ax+m=b.
由裴属定理,当gcd(a,m)|b时有解.
②扩展欧几里得求ax+my=gcd(a,m)的解,之后把x乘以b/gcd(a,m),即原方程得特解.
时间复杂度最坏为O(2logN),底数为2.
注意一点:如果要求最小正整数解,令x=c/d*x,直接输出(x%(b/d)+b/d)%(b/d)即可
answer
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b,d,x,y;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll ret=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return ret;
}
int main()
{
scanf("%lld %lld",&a,&b);
d=exgcd(a,b,x,y);
x=x/d;
printf("%lld",(x%(b/d)+b/d)%(b/d));
return 0;
}
answer
这题就是转换一下,由题x+am≡y+an(%L),a为跳的次数.
x+am=k\(1\)L+k①,y+an=k\(2\)L+k②
①-②得到(m-n)a+(x-y)=(k\(1\)-k\(2\))L=kL.
此时这个形式还是不太好看,m-n,x-y和L都是已知量,a,k是未知量.对应到上面ax+by=c的形式,这里的m-n为a,a为x,l为b,k为y,y-x为c(这个看代码会清楚一点),就可以用上面的结论了.
不过还有一个地方需要处理,我们不知道m-n和y-x的正负,而gcd要求是非负整数,若a为负数,那就得把a,c都变号,b本来就是正的,不用变也不能变.为什么不变b可以呢?首先它也要求非负,其次我们只需要x的值,y无所谓,所以变完后的式子与变之前算出来的y为相反数,无影响.(本人认为是本题最坑的地方,不过在我们推m-n,x-y的时候应该会有一点想法吧)
放代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll f,g,h,j,l,x,y;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll ret=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return ret;
}
int main()
{
scanf("%lld %lld %lld %lld %lld",&f,&g,&h,&j,&l);
ll a=h-j;ll b=l;ll c=g-f;
if(a<0)a=-a,c=-c;
ll d=exgcd(a,b,x,y);
if(c%d!=0)
{
cout<<"Impossible";
return 0;
}
x=c/d*x;
printf("%lld",(x%(b/d)+b/d)%(b/d));
}
- 二. 乘法逆元
1.定义:若a·x≡1%b,则称x为a在模b意义下的乘法逆元,记为a-1.
注意:并非所有的情况下都存在乘法逆元,但是当gcd(a,b)=1,即a,b互质时,存在乘法逆元
2.证明求取(a/b)%p等同于求(a*b-1)%p,从而使得求模运算除法转化为求一个数的逆元
已知(a/b)%p=m①,设(a·x)%p=m②.
①左边×b→(a/b·b)%p=((a/b)%p·b%p)%p
将(a/b)%p=m代入得到a%p=(m·b%p)%p③.
如果a,b<p,③式可转化为a=(m·b)%p④.
由②m<p,则(a·x)%p=m%p.
由④(b·m·x)%p=[(m·b)%p·x%p]%p,又(m·b)%p=a=a%p,∴(b·m·x)%p=(a·x)%p.
∴m%p=(x·b·m)%p.
∴(m·\(\frac{1}{m}\))%p=(m%p·\(\frac{1}{m}\)%p)%p=[(x·b·m)%p·\(\frac{1}{m}\)%p]%p=(b·x)%p.
由逆元定义可知,x为b在模p意义下的逆元.
证毕.
- 求乘法逆元
1.费马小定理
若gcd(a,p)=1且p为质数,由费马小定理得ap-1≡1(%p),即ap-1%p=(a×ap-2)%p=1.
所以ap-2就是a的逆元,然而......
从理论上讲ap-2是a在模p下的逆元.然而在实际计算中,ap-2可能是一个非常大的数,计算机在存储和处理这样的数时会⾯临困难。⽽且在模运算的情境下,我们更关⼼的是这个数在模p意义下的等价类
所以ap-2%p就是对ap-2进⾏模p运算,得到的结果是⼀个在0到p-1之间的数。这个结果所在的
等价类与ap-2所在的等价类是相同的,并且这个结果是该等价类中的最⼩⾮负代表元.
(PS:作者本人目前知识水平不高,不能给出等价类的解释,总之知道用ap-2%p作为逆元是科学的即可)
综上所述,ap-2%p为a在模p意义下的的逆元,用快速幂去求即可.
code模板
typedef long long ll;
ll quickpow(ll a,lln,ll p)//快速幂求a^n %p
{
ll ans=1;
while(n)
{
if(n&1)ans=ans*a%p;
a=a*a%p;
n>>=1;
}
return ans;
}
ll niyuan(ll a,ll p)//费马小定理求逆元
{
return quickpow(a,p-2,p);
}
注意:该方法使用的前提条件:模数为素数,且a不是p的倍数.
2.欧拉定理
当模数不是素数时,我们可以用欧拉定理来求解,再回顾一下欧拉定理:若gcd(a,n)=1,则aφ(n)≡1(%n).那么aφ(n)-1即为a在模n意义下的逆元,快速幂搞它.
code模板
int inv(int a,int p)
{
int phi=p,mod=p;
for(int i=2;i<=p/i;i++)
{
if(p%i==0)
{
phi=phi/i*(i-1);
while(p%i==0)
p/=i;
}
if(p>1)
phi=phi/p*(p-1);
phi--;
int res=1,t=a;
while(phi)
{
if(phi&1)
res=(res*t)%mod;
t=(t*t)%mod,phi>>=1;
}
return res;
}
}
时间复杂度O(\(\sqrt{p}\)+log p)
3.扩展欧几里得
根据逆元定义,若a×x≡1(%b),则称x为a在模b意义下的乘法逆元.那么,我们可以把原式转换成ax=by+1,移项得:ax-by=1,因为y为商,可以变个符号,原式变为ax+by=1.当gcd(a,b)=1时,方程才有整数解,因此,利用扩展欧几里得求逆元,要求a,b互质.但其实,只有a,b互质才会有a模b的逆元吧...
code模板
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
int t;
ll y,p;
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
int ans=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-(a/b)*y;
return ans;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld %lld",&y,&p);
ll x=0,k=0;
ll ok=exgcd(y,p,x,k);
if(ok==1)
printf("%lld\n",(x%p+p)%p);
else printf("-1\n");
}
}
4.线性求逆元(这位是dalao)
此方法比以上三种速度更快,若要求1,2,...,p-1中每个数关于p的逆元(p为质数),保险起见,还得用此法.
过程推导:
首先,1-1≡1(%p),显然成立.
设p=k×i+r,其中1<i<p,r<i,那么k是p/i的商,r就是p/i的余数即p%i
再放到模p意义下,则有:k×i+r≡0(%p).
两边同时乘以i-1×r-1,则:
k×r-1+i-1≡0(%p)
i-1≡-k×r-1(%p)
i-1≡-(p/i)×r-1
i-1≡-(p/i)×(p%i)-1
设t=m/i,k=m%i,有:m=i*t+k
即i*t+k≡0(%m)
即k≡-i*t(%m)
两边同时除以i*k
有1/i≡-t/k(%m)
将k,t带入有inv[i]≡-m/i*inv[m%i] (%m)
为防止有负数,有inv[i]=(m-m/i)*inv[m%i])%m
于是就有了一个递推式.
code模板
我们往往需要的是不大于p的正整数,所以加上一个p,因为1<i<p,所以p/i一定小于p
ny[1]=1;
for(int i=2;i<p;i++)
{
ny[i]=(long long)(p-p/i)*ny[p%i]%p;//模p不要忘记
}
P3811乘法逆元此题推荐使用线性求.
方法总结:
| 方法 | 限定条件 | 时间复杂度 | 备注 |
|---|---|---|---|
| 费马小定理 | 模数为素数 | O(log n) | |
| 欧拉定理 | a,p互质 | 差不多是O(\(\sqrt{n}\)) | |
| 扩展欧几里得 | a,p互质 | 据说为 O(log n) | 可以在求解过程中判断是否互质 |
| 线性递推 | 模数为素数 | O(n) |
制作不易,点个推荐再走呗(o°ω°o)

浙公网安备 33010602011771号