数论学习笔记——最大公约数、乘法逆元

  • 一.最大公约数

  • [求最大公约数]

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;
}
4.多个数的最小公倍数

一个套路,先算两个的,再与第三个继续求。

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)即可

NOIP2012同余方程

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)

posted @ 2025-02-08 16:52  Crab2016  阅读(77)  评论(0)    收藏  举报