【数论】乘法逆元

【数论】乘法逆元

Definition

对于一个数 x 和一个模数 p,若存在一个数字 y,满足

x×y1(modp)

则称 yx 在模 p 意义下的逆元,记做 x1 y(modp)

一个数字逆元在模意义下的运算中可以完全取代该数字的倒数。例如 xy x×y1(modp),其中 y1 代表 y 的逆元。

Algorithm

Lemma

首先需要指出的是,一个数 x 在模 p 意义下存在逆元,当且仅当 xp 互质。

Proof

这里只证明当 xp 不互质时不存在逆元。对于逆元的存在性,由于下面的部分给出了逆元的构造算法,这就已经证明了在互质时逆元是存在的。

反证法,设对于任意的 xZ+,存在 x1Z+

x×x11(modp)      (1)

gcd(x, p)=d1      (2)

根据同余的定义,(1) 可以写成:

x×x1=k×p+1      (3)

其中 k 是一个非负整数。

(3) 的等号两侧同时除以 (2) 中的 d

x×x1d = k×p+1d      (4)

整理得到

xd×x1 = pd×k+1d      (5)

因为 d=gcd(x,p),所以 d 一定是 xp 的因数。所以

xdpd 都是整数,进而 pd×k 是整数,xd×x1 是整数。

而因为 d1,所以 1d 一定不是整数,因此 pd×k+1d 不是整数。

于是等号左侧是整数,等号右侧不是整数,左侧一定不等于右侧,产生矛盾。这就矛盾证明了 x 在模 p 意义下存在逆元仅当 xp 互质。


以下介绍求逆元的算法:

求单个数字的逆元

Algorithm 1

x×x1 1(modp)

显然可以转化成方程

x×x1=1+kp

y=k,移项得到

x×x1+y×p=1

注意到这个式子就是扩展欧几里得算法所求的式子

ax+by=1

只不过 x 作为一个常数,是欧式式子里的 a,同理 p 是欧式式子里的 b。使用扩展欧几里得算法求解上面这个式子即可。时间复杂度 O(logx)

Algorithm 2

根据欧拉定理

xϕ(p)1(modp)

其中 ϕ 为欧拉函数,ϕ(p) 表示小于 p 的正整数中与 p 互质的数的个数。

等式两侧同乘 x1 可以得到

xϕ(p)1x1(modp)

显然当 p 是一个质数时,ϕ(p)=p1,这时可以 O(1) 算出 ϕ(p)1 的值,即可用快速幂 O(logx) 求出 x 的逆元。这个算法好写好记,常数也较小。一般当 pint 范围内的质数时选择此算法。当 p 不在 int 范围内时,由于快速幂时需要两个 long long 相乘,会爆精度。

有关欧拉定理的证明可以看这里

n 以内所有正整数模 p 的逆元

显然,由于 n 以内所有正整数都有在模 p 意义下的逆元,所以 pn 以内的所有数互质。

结论:设 invii 的逆元,则有递推式

invipi×invpmodi(modp)

边界条件为

inv1=1

Proof

首先 inv1=1 显然成立。

对于 i>1,写出 p 除以 i 的带余除法表达式:

p=ki+r

其中 r[0,i1]

等式两侧对 p 取余数,有

0ki+r(modp)

移项得到

rki(modp)

两侧同乘 i1×r1,整理得到

i1kr1(modp)

由于 k=pir=pmodi,所以原式得证。

又因为 r<i,所以在计算 invi 时,invr 已经被计算完成,所以上述递推可以完成。

证毕。

这样做的时间复杂度显然是 O(n)

n! 的逆元

因为 (n!)11n!i=1nn1,所以线性筛出 n 以内所有数字的逆元时,可以顺便求出 n! 的逆元。时间复杂度 O(n)

Code

Ex_Gcd

#include <iostream>

typedef long long int ll;

ll x, p;

void Ex_gcd(const ll a, const ll b, ll &X, ll &Y);

int main() {
  std::cin >> x >> p;
  ll a, b;
  Ex_gcd(x, p, a, b);
  std::cout << (a % p + p) % p << std::endl;
  return 0;
}

void Ex_gcd(const ll a, const ll b, ll &X, ll &Y) {
  if (b == 0) {
    X = 1; Y = 0;
  } else {
    Ex_gcd(b, a % b, Y, X);
    Y -= a / b * X;
  }
}

欧拉定理

#include <iostream>

typedef long long int ll;

ll X, p;

ll mpow(ll x, ll y);

int main() {
  std::cin >> X >> p;
  std::cout << mpow(X, p - 2) << std::endl;
  return 0;
}

ll mpow(ll x, ll y) {
  ll _ret = 1;
  while (y) {
    if (y & 1) (_ret *= x) %= p;
    y >>= 1;
    (x *= x) %= p;
  }
  return _ret;
}

线性求逆元

这里的 factinv 即为阶乘逆元。

#include <cstdio>

const int maxn = 3000005;

int n, p;
int inv[maxn], factinv[maxn];

int main() {
  scanf("%d%d", &n, &p);
  factinv[1] = inv[1] = 1;
  printf("%d\n", 1);
  for (int i = 2; i <= n; ++i) {
    inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
    printf("%d\n", inv[i]);
    factinv[i] = 1ll * factinv[i - 1] * inv[i] % p;
  }
  return 0;
}
posted @   一扶苏一  阅读(833)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
历史上的今天:
2019-01-06 【DP】【CF1097D】 Makoto and a Blackboard
点击右上角即可分享
微信分享提示