初等数论——同余

前置

模运算

定义: a%b(amodb) ,表示 a 除以 b 的余数。

加法: (a+b)%p
减法: (ab+p)%p 。加 p 是为了防止负数。
乘法: (a×b)%p
除法无法直接运算,要用逆元(在下面会讲到)。
平方运算: 快速幂

模运算满足: 结合律,交换律,分配律

同余的定义及其性质

定义:a,b 除以 m 的余数相同,则称 a,bm 同余,记作 ab(modm)

由对于模n同余的所有整数组成的这个集合称为同余类(congruence class或residue class) 。

m 的同余类一共有 m 个,它们构成 m完全剩余系

1m 中与 m 互质的数代表的同余类共有 φ(m) 个,它们构成了 m 的简化剩余系。

性质1: 满足对称性,同加性,同乘性,同幂性。

性质2:ab(modp),ca,cb,则 ab(modmgcd(m,c))

费马小定理

p 是质数,aZ,apa(modp) 。当 a,p 互质时,则有 ap11(modp)

引理:当 p 是质数时,其因子只有 1p 两个。因此,若两个数相乘是 p 的倍数,其中必然至少有一个是 p 的倍数。
a 不是 p 的倍数时,不存在 xy1x,y<p 使得 a×ya×x(modp)xy<pp 的倍数,与 1x,y<p 的限制矛盾。
进一步地,考虑 1p1 所有数,它们乘以 a 之后在模 p 意义下互不相同,说明仍得 1p1 所有数。
因此,i=1p1ii=1p1ai(modp)。又因为 i=1p1i显然不是 p 的倍数,所以费马小定理成立。

欧拉定理

若正整数 a,n 互质,aφ(n)1(modn) 其中 φ(n) 为欧拉函数。

x1,x2xφ(n) 是模 n 的简化剩余系,那 ax1,ax2,axφ(n) 也是模 n 的简化剩余系。
因为 gcd(a,n)=1ax1a˙x2axφ(n)x1,x2xφ(n)(modn) ,所以欧拉定理成立。

可以发现费马小定理是欧拉定理的一种特殊情况。

扩展欧拉定理

a,n 互质,ababmodφ(n)

a,n 不互质,当 b>φ(n) 时,abmodφ(n)+φ(n)

因为证明有些复杂,见oi wiki

扩展欧拉定理可以用来降幂,当幂太大时,此公式可以减小复杂度,而当 b<φ(n) 幂比较小,就没有必要降幂了。

例题:P4139 上帝与集合的正确用法

线性同余方程

裴蜀定理

定义: a,b 是不全为零的整数,对于任意整数 x,ygcd(a,b)ax+by ,且存在整数 x,y,使得 ax+by=gcd(a,b)

逆定理:a,b 是不全为零的整数,若 d>0a,b 的公约数,且存在整数 x,y,使得 ax+by=d,则 d=gcd(a,b)

特殊地,设 a,b 是不全为零的整数,若存在正整数 x,y ,使得 ax+by=1 ,则 a,b 互质。

当有 n 个数时,此定理依然成立。

例题: P4549 【模板】裴蜀定理

code
#include<bits/stdc++.h>
using namespace std;
int n;
int gcd(int x,int y)
{
	return y==0?x:gcd(y,x%y); 
}
int main()
{
	scanf("%d",&n);
	int x,y;
	cin>>x;
	for(int i=2;i<=n;i++)
	{
		cin>>y;
		x=gcd(x,y);
	}
	cout<<abs(x)<<endl;
	return 0;
 } 

扩展欧几里得算法

上文裴蜀定理,讲述了存在整数 x,y,使得 ax+by=gcd(a,b) ,这里讲述如何求解 x,y

我们设 d=gcd(a,b)。可以用辗转相除法法求出两个数的 gcd ,所以我们假设它的另一个解是x2,y2,满足

b×x2+(amodb)×y2=gcd(a,b)

也就是

b×x2+(amodb)×y2=a×x+b×y

又因

amodb=ab×(a/b)

代入拆开得

a×y2+(x2y2×(a/b))=a×x+b×y

可得一个解

x=y2,y=x2y2(a/b)

现在发现只需要求出 x2,y2 而我们观看一下求 x,y 的式子,就是辗转相除法的式子,所以我们只需递推求一下 x2,y2,x3,y3等。

考虑一下递推边界,当递推到b=0 时,

a×x+b×y=gcd(a,b)

可转换成

a×x=gcd(a,b)=a

显然 x=1yZ 时 ,方程成立。

这样就求出了方程的一组特解,我们设为 x0,y0

显然不定方程 ax+by=gcd(a,b) 有无穷解,所以要求通解形式。

可以知道 Δ(ax)+Δ(by)=0 ,设 Δ=|Δ(ax)|=|Δ(by)|,那 a,bΔlcm(a,b)Δ ,所以 Δxlcm(a,b)a=bd 的倍数,Δylcm(a,b)b=ad 的倍数。

所以通解为:

{x=x0+bd×ky=y0ad×k

这里 kZ

实际做题时,会让求 x 的最小正整数解,设 t=bd

x=(xmodt+t)modt

特解的数据范围:ycx 的文章 关于 exgcd 求得特解的数值范围

线性同余方程

定义:形如 a×xc(modb),a,bZ 的方程叫做线性同余方程

求法:

原式转换成 :

a×x+b×y=cyZ

这个式子和扩展欧几里得算法可以求的式子有点关联。

a×x+b×y=gcd(a,b)

d=gcd(a,b) ,由裴蜀定理可知,方程有解当 dc

原式又可转成:

a×gcd(a,b)c×x+b×gcd(a,b)c×y=gcd(a,b)

这样用扩展欧几里得算法就可以求出。

模板: P1082 [NOIP2012 提高组] 同余方程

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int c,d,x,y;
int exgcd(int a,int b,int &x,int &y)
{
	if(b==0)
	{
		x=1,y=1;
		return a;
	}
	int t=exgcd(b,a%b,y,x);
	y-=(a/b)*x;
	return t;
}
signed main()
{
	scanf("%lld%lld",&c,&d);
	exgcd(c,d,x,y);
	printf("%lld",(x%d+d)%d);
	return 0;
}

线性同余方程组(中国剩余定理)

{xa1(modm1)xa2(modm2)xan(modmn)

中国剩余定理(crt)

上式子中 aZ,mN,(a,m)=1

求解方法:

M=m1×m2×mn

bi=Mmibi1bimi 下的逆元。

x=i=1nai×bi×bi1

证明:

尝试为每个同余方程设一个 xi,xiai(modmi),可以知道 xi0(modmj)ji,记为条件一。

xibi 的倍数,如果要满足条件一 xi=bi×ai 即可,
那要满足上面新设的同余方程,让 xi×bi1 即可,如果要让所以 xi 合成一个 x,加起来就可以了。

扩展中国剩余定理(excrt)

上面没看懂?没关系有更好理解并适用更广的扩展中国剩余定理(excrt),它不要求模数互质。

本质就是合并方程,我们把两个方程合并成新一个方程,依次类推就可以求解方程组了。

具体的说:合并方程 xa1(modm1)xa2(modm2)

x=a1+p1×m1,所以

a1+p1×m1=a2+p2×m2

即:

p1×m1+p2×m2=a2a1

这个地方可以用 exgcd 求解:

x×m1+y×m2=gcd(m1,m2)

设:d=gcd(m1,m2),c=a2a1

由上文扩欧的知识知道:

x=x+k×lcm(m1,m2)m1=x+k×m2d

然后把 x 带入求出 p1=x×cd+b1

根据 裴蜀定理gcd(m1,m2) 不是 a2a1 因数时,方程无解。

现在求出了一个特解 p,两个同余方程就改写成一个:

xq(modlcm(m1,m2))

模数为 lcm(m1,m2) 的证明:

引用:阮行止的洛谷题解

从线性代数的角度讲,这个通解的构造方式是十分平凡的。对 lcm(m1,m2) 取模的结果,将整个整数集划分成了 lcm(m1,m2) 个等价类,哪个等价类里面有特解,那整个等价类肯定全都是解。一人得道,鸡犬升天。接下来唯一需要说明的事情就是:为什么任意一个完全剩余系里面,只会有一个解?这个问题等价于:为什么 0,1,2,,lcm(m1,m2)里面,只有一个解?

证明解的唯一性,常常采用这样一种手段:假设 x,y 都是原问题的解,然后经过一系列推理,得到 x=y,于是解的唯一性就不言而喻了。我们也采用这种手段来解决唯一性问题。设上述集合里面有 0x,ylcm(m1,m2) 满足:

{xa1(modm1)xa2(modm2),{ya1(modm1)ya2(modm2)

不妨设 xy 那立刻就可以发现:

{xa1(modm1)xa2(modm2),{ya1(modm1)ya2(modm2){(xy)modm1=0(xy)modm2=0lcm(m1,m2)|(xy)

x,y 都是小于 lcm(m1,m2) 的数,它们的差也必然要小于 lcm(m1,m2) 。但 (xy) 又要被 lcm(m1,m2) 整除,那怎么办?只有 xy=0 ,也就是 x=y 。到此为止,我们证明了:一个完全剩余系中,有且仅有一个解。以上就是整个 exCRT 算法的全部数学基础。

模板:

P1495 【模板】中国剩余定理(CRT)/ 曹冲养猪

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[20],b[20],ans,M=1,t[20],m[20];
int x,y;
int exgcd(int a,int b)
{
	if(b==0)
	{
		x=1,y=0;
		return a;
	}
	exgcd(b,a%b);
	int t=y;
	y=x-a/b*y;
	x=t;
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>b[i]>>a[i],M*=b[i];
	for(int i=1;i<=n;i++)
	{
		exgcd(M/b[i],b[i]);
		t[i]=x;
		t[i]=(t[i]%b[i]+b[i])%b[i];
	}
	for(int i=1;i<=n;i++)
	{
		ans+=a[i]*(M/b[i])*t[i]%M;
	}
	cout<<ans%M<<endl;
	return 0;
}

P4777 【模板】扩展中国剩余定理(EXCRT)

code
#include<bits/stdc++.h>
using namespace std;
typedef __int128_t ll;
const int N=1e7+10;
ll gcd(ll aa,ll bb)
{
    if(!bb) return aa;
    return gcd(bb,aa%bb);
}
ll lcm(ll aa,ll bb)
{
    return aa/gcd(aa,bb)*bb;
}
ll exgcd(ll aa,ll bb,ll &x,ll &y)
{
    if(!bb)
    {
        x=1,y=1;
        return aa;
    }
    ll d=exgcd(bb,aa%bb,x,y);
    ll z=x;
    x=y;
    y=z-(aa/bb)*y;
    return d;
}
long long a[N],b[N],n;
int main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld%lld",&a[i],&b[i]);
    ll a1=a[1],b1=b[1];
    for(int i=2;i<=n;i++)
    {
        ll a2=a[i],b2=b[i],x,y;
        ll c=b2-b1;
        ll d=exgcd(a1,a2,x,y);
        if(c%d)
            return puts("-1"),0;
        c/=d;
        x=x*c%(a2/d);
        if(x<0) x+=(a2/d);
        ll mod=lcm(a1,a2);
        b1=(a1*x+b1)%mod;
        if(b1<mod)  b1+=mod;
        a1=mod;
    }
    printf("%lld",(long long)((b1%a1)+a1)%a1);
    return 0;
}

乘法逆元

定义

如果一个线性同余方程 ax1(modb)x 称为 amodb 的逆元,记作 a1

应用: 上文提到除法无法直接取模,而用乘法逆元就可以求出。

根据定义可知 a×a1=1 ,那

a÷b=a×1b=a×b×b1b=a×b1

求解方法

求单个数的乘法逆元

当模数 p 为质数时,可以用 费马小定理 求解

由定义知,xa 的乘法逆元

ax1(modp)

再由费马小定理得:

axap1(modp)

所以

xap2(modp)

用快速幂就可以求出

code
int qpow(long long a, int b) {
  int ans = 1;
  a = (a % p + p) % p;
  for (; b; b >>= 1) {
    if (b & 1) ans = (a * ans) % p;
    a = (a * a) % p;
  }
  return ans;
}
int main()
{
	a=qpow(a,p-2);//求 a 的逆元
}

p 不为质数, gcd(a,p)=1 时,可以用扩展欧几里得算法求出。(p 不为质数时也可以求)。

求乘法逆元本质上就是求线性同余方程,用求线性同余方程的方法求就可以了。

线性求 1n 的逆元

用递推来求逆元,当递推到 i 时,设 k=pi,j=pmodi,所以

p=k×i+j

由此又可以推出

k×i+j0(modp)

两边同乘 i1×j1 得:

j1×k+i10(modp)

所以

i1k×j1(modp)

既然是递推式,就要关注递推的起始项,当 i=1 时,1 的模任何数的逆元都是 1

code
inv[1] = 1;
for (int i = 2; i <= n; ++i) {
  inv[i] = (p - p / i) * inv[p % i] % p;
}

代码中 pp/i 是为了防负数。

求任意 n 个数的逆元

要求 a1an 的逆元。

定义两个数组 ssvsi 表示前 i 个数的乘积, svi 表示前 i 个数的逆元的乘积,那这样 ai 的逆元就是 si1×svi

递推边界 s0=1svn=sn 的逆元。

code
s[0] = 1;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] * a[i] % p;
sv[n] = qpow(s[n], p - 2);
for (int i = n; i >= 1; --i) sv[i - 1] = sv[i] * a[i] % p;
for (int i = 1; i <= n; ++i) inv[i] = sv[i] * s[i - 1] % p;

原根

posted @   houguo  阅读(1172)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示