当前位置: 首页 > news >正文

关于数论乘法逆元及相关知识点

在求解a/b%m时,可以转化为(a%(b*m))/b,转化过程如下

令k = (a/b)/m(向下取整), x = (a/b)%m;

a/b = k*m + x (x < m);

a = k*b*m + b*x;

a%(b*m) = b*x;

a%(b*m)/b = x;

得证: a/b%m = a%(b*m)/b;(公式适用于很多情况:m不必是素数,b和m也不必互质)

上面的公式适用于b较小,a需要在线求且较大的时候;

比如:求a/b的值,且另a = x^k, 求a的过程随时会爆精度所以需要对a进行取模,但是求的过程中如果是直接对a进行%mod,之后再进行a/b,会出现错误的。正确的做法是根据上述公式,在求a过程中对(b*m)取模,之后再除以一次b即可。[ a/b%m = (a%(b*m))/b%m ]


所有的除法取模问题都可以用这种方法,但是当b很大的时候,则会出现爆精度问题,所以引出乘法逆元,将除法取模转换为乘法取模。

b存在乘法逆元的充要条件是b与模数m互质。设令c为b的逆元,即b*c ≡ 1(mod m);

解释:满足同余方程,b*c和1对m的模数相同,即b*c对m取余为1(m > 1);

那么 a/b = (a/b)*1 = (a/b)*(b*c)(mod m) = a*c(mod m);

即,除以一个数对m取模等于乘以这个数的逆元对m取模;


三种求逆元的方法&:

1.逆元求解利用扩欧。
2.当m为质数的时候直接使用费马小定理,m非质数时使用欧拉函数。
3.当m为质数的时候,使用神奇的线性方法。

扩展&:

1.利用乘法逆元求解组合数及组合数的其它求法。

2.线性时间求阶乘的逆元。


扩展欧几里得算法.


首先了解一下扩展欧几里得算法,

已知 a 和 b 的最大公约数是 gcd,那么,我们一定能够找到这样的 x 和 y ,使得: a*x + b*y = gcd 这是一个不定方程(其实是一种丢番图方程),有多解是一定的,但是只要我们找到一组特殊的解 x0 和 y0 那么,我们就可以用 x0 和 y0 表示出整个不定方程的通解:
    x = x0 + (b/gcd)*t; ((b/gcd)是b的因子,能表示的数只能多而不会少,下面同理)
    y = y0 – (a/gcd)*t;

现在,我们知道了一定存在 x 和 y 使得 : a*x + b*y = gcd , 那么,怎么求出这个特解 x 和 y 呢?只需要在欧几里德算法的基础上加点改动就行了。
我们观察到:欧几里德算法停止的状态是: a = gcd , b = 0 ,那么,这是否能给我们求解 x, y 提供一种思路呢?这时候,只要 a = gcd 的系数是 1 ,只要 b 的系数是 0 或者其他值(无所谓是多少,反正任何数乘以 0 都等于 0 但是a 的系数一定要是 1),这时,我们就会有: a*1 + b*0 = gcd;
当然这是最终状态,但是我们是否可以从最终状态反推到最初的状态呢?
假设当前我们要处理的是求出 a 和 b的最大公约数,并求出 x 和 y 使得 a*x + b*y= gcd ,而我们已经求出了下一个状态:b 和 a%b 的最大公约数,并且求出了一组x1 和y1 使得: b*x1 + (a%b)*y1 = gcd , 那么这两个相邻的状态之间是否存在一种关系呢?
我们知道: a%b = a - (a/b)*b(这里的 “/” 指的是整除,例如 5/2=2 , 1/3=0),那么,我们可以进一步得到:
        gcd = b*x1 + (a-(a/b)*b)*y1;
            = b*x1 + a*y1 – (a/b)*b*y1;
            = a*y1 + b*(x1 – a/b*y1);
对比之前我们的状态:求一组 x 和 y 使得:a*x + b*y = gcd,是否发现了什么?
这里:
        x = y1;
        y = x1 – a/b*y1;

上代码:

递归版e_gcd();




还有非递归版的;



前提条件:b与m互质,存在唯一解。

扩展欧几里德算法,我们既可以求出最大公约数,还可以顺带求解出使得: a*x + b*y = gcd 的通解 x 和 y
扩展欧几里德有什么用处呢?
求解形如 a*x +b*y = c 的通解,但是一般没有谁会无聊到让你写出一串通解出来,都是让你在通解中选出一些特殊的解,乘法逆元便是。


a*x ≡ 1(mod m);

这里称 x 是 a 关于 m 的乘法逆元, 这怎么求?可以等价于这样的表达式: a*x + m*y = 1;
看出什么来了吗?没错,当gcd(a , m) != 1 的时候是没有解的, 这也正是 a*x + b*y = c 有解的充要条件: c % gcd(a , b) == 0;
接着乘法逆元讲,一般,我们能够找到无数组解满足条件,但是一般是让你求解出最小的那组解,怎么做?我们求解出来了一个特殊的解 x0 那么,我们用 x0 % m其实就得到了最小的解了。为什么?
可以这样思考:
x 的通解不是 x0 + m*t 吗?
那么,也就是说, a是和 a 关于 m 的逆元是关于 m 同余的,那么根据最小整数原理,一定存在一个最小的正整数,它是 a 关于m 的逆元,而最小的肯定是在(0 , m)之间的,而且只有一个,这就好解释了。
有时候我们得到的特解 x0 是一个负数,还有的时候我们的 m 也是一个负数这怎么办?
当 m 是负数的时候,我们取 m 的绝对值就行了,当 x0 是负数的时候,他模上 m 的结果仍然是负数(在计算机计算的结果上是这样的,虽然定义的时候不是这样的),这时候,我们仍然让 x0 对abs(m) 取模,然后结果再加上 abs(m) 就行了,于是,我们不难写出下面的代码求解一个数 a 对于另一个数 m 的乘法逆元。



当然略微有点长了,比赛时用下面的就足以解决很多。

int ex_gcd(int a, int b, int &d, int &x, int &y){
	if(b == 0){
		x = 1; y = 0; d = a;
		return;
	}
	ex_gcd(b, a%b, d, y, x);	//不断交错x和y
	y -= a/b*x;	//当递归完成return时,y值return给了x, x值return给了y
}
int clac(int a, int m){
	int x, y, d;
	ex_gcd(a, m, d, x, y);
	return (m + x%m)%m;
}


通过费马小定理或者欧拉函数去求.


1.费马小定理求逆元:

在m是素数的情况下,对任意整数x都有x^m ≡ x(mod m)。 
如果x为整数且无法被m整除,则有x^(m−1) ≡ 1(mod m)。 
所以可以在m为素数的情况下求出一个数的逆元,x * x^(m−2) ≡ 1(mod m),x^(m−2)即为逆元。

推导过程如下:

然后就可通过快速幂求得逆元。

quickM(int base, int b);


2.欧拉函数求逆元:
令ϕ(m)表示小于等于m且与m互素的正整数的个数(特例(ϕ(1) = 1))。
如果x和m互质,则有x^ϕ(m) ≡ 1(mod m),即x * x^(ϕ(m)−1) ≡ 1(mod m),x^(ϕ(m)−1)即为x的逆元。 
在m为质数的情况下,ϕ(m) = m−1,即为费马小定理。

先介绍一下欧拉函数的一些知识(能学一点是一点...)

φ(x) = x*(1-1/p(1))(1-1/p(2))(1-1/p(3))(1-1/p(4))…..(1-1/p(n)) 其中p(1),p(2)…p(n)为x的所有质因数;x是正整数; 注意:每种质因数只有一个。φ(1)=1(唯一和1互质的数,且小于等于1)。

欧拉函数的性质:
(1)p^k型欧拉函数:
若N是质数p(即N=p),φ(N) = φ(p) = p-p^(1-1) = p-1。
若N是质数p的k次幂(即N=p^k),φ(N)=p^k-p^(k-1) = (p-1)*p^(k-1)。
(2)m*n型欧拉函数:
设n为正整数,以φ(n)表示不超过n且与n互素的正整数的个数,称为n的欧拉函数值。若m,n互质,φ(m*n)=(m-1)*(n-1)=φ(m)*φ(n)。
(3)特殊性质:
若n为奇数时,φ(2n)=φ(n)。
(4)欧拉定理:
对于任何两个互质的正整数x,m(m>2)有:x^φ(m) ≡ 1(mod m)。
(5)扩展(费马小定理)
当m=p且x与素数p互质(即:gcd(x,p)=1)则上式有: x^(p-1) ≡ 1(mod p)。
(6)欧拉函数的延伸:
小于或等于n的数中,与n互质的数的总和为:φ(n) * n/2 (n>1)。

证明就不给出了,下面是模板代码:

求单个数的欧拉函数值:

int Euler(int n){
	int ans = n;
	//直接扫一般会超时,所以这里利用任何一个合数都至少
	//有一个不大于根号n的素因子,所以只需遍历到根号n即可 
	for(int i = 2; i*i <= n; ++i){
		if(n%i == 0){
			ans = ans/i*(i-1);	//先除后乘防止中间数据溢出
			while(n%i == 0) n /= i; 
		}
	}
	if(n > 1) ans = ans/n*(n-1);
	return ans;
}


多次用到欧拉函数值,可通过筛选预先打表(时间复杂度O(n·logn)):
void getEuler(){
	int euler[maxn];
	for(int i = 1; i <= maxn; ++i)
		euler[i] = i;
	for(int i = 2; i <= maxn; i+=2)
        euler[i] /= 2; 	//其实相当于进行了一次euler[i]/2*(2-1);
	for(int i = 3; i <= maxn; ++i)
    {
        if(euler[i] == i) //未被筛到, 即是素数, 则用此素数来筛
        {
            for(int j = i; j <= maxn; j+=i)
            {
                euler[j] = euler[j]/i*(i-1);
            }
        }
    }
} 

Last,

求出模数m的欧拉函数值后,进行一次快速幂 quickM(base,φ(n)-1)求得逆元。


线性时间内求范围内所有整数的逆元.


当m不是很大的时候(数组能够存起来的时候)可以线性时间求逆元。规定m为质数,
首先 1^(−1) ≡ 1(mod m)
然后我们设 m = k*x + r, r < x, 1 < x < p;
再将这个式子放到mod m意义下就会得到
k*x + r ≡ 0 (mod m)
两边同时乘上 x^(−1) * r^(−1)就会得到
k * r^(−1) + x^(−1) ≡ 0 (mod m)
x^(−1) ≡ −k * r^(−1) (mod m)
x^(−1) ≡ -(m/x) * (m%x)^(−1) (mod m)
从头开始扫一遍即可,时间复杂度O(n);

int inv[maxn];
inv[1] = 1;
for(int i = 2; i < maxn; ++i)
    inv[i] = (-p/i + p) * inv[p%i] % p;


扩展:

利用乘法逆元求解组合数及组合数的其它求法.


首先声明C(n, m)是指n个中取m个,网上都没统一写法,真是的...

求组合数常用的公式为C(n, m) = n!/((n-m)! * m!);

当n, m都很小的时候可以利用杨辉三角直接求。
或者通过下面推导公式递归求。
C(n+1, m) = C(n, m) + C(n, m-1);
C(n, m) = C(n-1, m) + C(n-1, m-1);

简单的递归代码:

#include <stdio.h>
int calc(int n, int m){
	if(n < m || n == 0) return 0;
	if(n == m || m == 0) return 1;
	return calc(n-1, m) + calc(n-1, m-1);
}
int main(){
	int n, m;
	while(scanf("%d %d", &n, &m)){
		printf("%d\n", calc(n, m));
	}
	return 0;
}

再就是最常用的利用乘法逆元进行求解。

//可预先打出指定范围内阶乘的表
void init(){
	fact[0] = 1;
	for(int i = 1; i <= maxn; ++i)
	fact[i] = fact[i-1]*i % mod;
}

1. (n!/(m!*(n-m)!)) % mod = x %mod ,先对算出n!、m!、(n-m)!对mod取模的余数,就转换为a/b = x%mod;因为m为素数,所以等价于b*x +mod*y = gcd(b,mod); 然后用扩展的欧几里得定理算出 b*x0 +mod*y0 = 1的特解x0,x再进而确定 =(mod + x0%mod) %mod; 则a/b = a*x%mod;

LL e_gcd(LL a, LL b, LL &x, LL &y){
	if(b == 0){
		x = 1; y = 0;
		return a;
	}
	LL ans = e_gcd(b, a%b, y, x);
	y -= x*(a/b);
	return ans;
}
LL clac(LL a, LL m){
	LL x, y;
	e_gcd(a, m, x, y);
	return (m + x%m)%m;
}
LL C(int n, int m){
	return fact[n]*clac(fact[m]*fact[n-m]%mod, mod)%mod;
}


2.如果mod是素数 则b的逆元其实就是b^(mod-2)即 (m!*(n-m)!)的逆元为 (m!*(n-m)!)^(mod-2);

LL quickM(LL a, LL b){
	LL base = a%mod, ans = 1;
	while(b){
		if(b&1) ans = ans*base%mod;
		base = base*base%mod;
		b >>= 1; 
	}
	return ans;
}
LL C(int n, int m){
	return fact[n]*quickM(fact[m]*fact[n-m]%mod, mod-2)%mod;
}

3.当n和m比较大而mod为素数且比较小(10^5左右)的时候,可以用Lucas定理计算.

Lucas定理:

A、B是非负整数,模数mod = p(p是质数)。A、B写成p进制:
A = a[n]a[n-1]...a[0];
B = b[n]b[n-1]...b[0];
则组合数C(A,B)与C(a[n], b[n])*C(a[n-1], b[n-1])*...*C(a[0], b[0])(mod p)同余
即:Lucas(n, m, p) = c(n%p, m%p)*Lucas(n/p, m/p, p);


模板代码:

LL quickM(LL base, LL b, LL p) {
	LL ans = 1;
	while(b) {
	    if(b&1) ans = (ans * base) % p;
	    base = (base*base) % p;
	    b >>= 1;
	}
	return ans;
}

//n,m过大不能打表只能在线求阶乘
LL Comb(LL a, LL b, LL p) {
	if(a < b) return 0;
	if(a == b) return 1;
	if(b > a - b) b = a - b;
	
	LL ans = 1, ca = 1, cb = 1;
	for(LL i = 0; i < b; ++i) {
	    ca = (ca * (a-i)) % p;
	    cb = (cb * (b-i)) % p;
	}
	ans = (ca*quickM(cb, p-2, p)) % p;
	return ans;
}

LL Lucas(int n, int m, int p) {
	LL ans = 1;
	while(n && m && ans) {
		ans = (ans*Comb(n%p, m%p, p)) % p;
		n /= p;
		m /= p;
	}
	return ans;
}


4.打表求阶乘数逆元的新方法.

打一个1~n的阶乘的逆元的表,假如n!的逆元叫做f[n],可以先用费马小定理、扩展欧几里得等 
求出f[n],再用递推公式求出前面的项。
我们记数字 x 的逆元为 f(x) (mod m)。
  因为 n! = (n-1)! * n;
  所以 f(n!) = f((n-1)! * n) = f((n-1)!) * f(n);
  所以 f((n-1)!) = f(n!) * f(f(n)) = f(n!) * n;  (数的逆元的逆元就是它自身)
这样子我们就可以用后项推出前面的项了。

LL quickM(LL base, LL b)
{  
    LL ans = 1;  
    while(b)
	{  
        if(b&1) ans = (ans * base) % mod;  
        base = (base*base) % mod;  
        b >>= 1;
    }  
    return ans; 
}
void init()
{
    fact[0] = 1;
    for(int i = 1; i <= maxn; ++i)
    fact[i] = fact[i-1]*i%mod;
    fiv[maxn] = quickM(fact[maxn], mod-2);
    for(int i = maxn-1; i >= 0; --i)
    {
        fiv[i] = fiv[i+1]*(i+1);
        fiv[i] %= mod;
    }
}
LL C(int n, int m)
{
	if(m > n) return 0;
	return fact[n]*fiv[n-m]%mod*fiv[m]%mod;
}

5.发现一个近乎完美的init(): 阶乘、整数逆元、阶乘逆元,一次O(n)即可。

void init()
{
	fact[0] = fact[1] = 1;
	fiv[0] = fiv[1] = 1; 
	inv[1] = 1;
	for(int i = 2; i <= maxn; ++i)  
	{
		//递推保存fact阶乘,递推求inv和fiv各个逆元 
	    fact[i] = fact[i-1]*i%mod;
	    inv[i] = (mod-mod/i)*inv[mod%i]%mod;
	    fiv[i] = inv[i]*fiv[i-1]%mod;
	}
}

Over~~~


参考博文:

http://blog.csdn.net/yukizzz/article/details/51105009

http://blog.csdn.net/zhjchengfeng5/article/details/7786595

http://blog.csdn.net/acdreamers/article/details/8220787

http://www.cnblogs.com/frog112111/archive/2012/08/19/2646012.html

http://blog.csdn.net/u010582475/article/details/47707739

http://www.cnblogs.com/handsomecui/p/4755455.html

http://www.cnblogs.com/PJQOOO/p/3875545.html

http://blog.csdn.net/pi9nc/article/details/9615359

http://www.cnblogs.com/zxndgv/archive/2011/09/17/2179591.html

http://www.cnblogs.com/vongang/archive/2012/12/02/2798138.html

http://www.lofter.com/tag/%E9%80%86%E5%85%83

http://blog.csdn.net/jeromiewn/article/details/54799318

相关文章:

  • ERP对部门经理的好处有哪些
  • Meisell-Lehmer算法(求1...n范围内的素数个数)
  • Oracle 正确删除archivelog文件
  • HDU 5898 数位DP
  • topas解析(AIX)
  • Tarjan算法 模板
  • SQL应用与开发:(六)函数和表达式的使用
  • QDU 帅气的HYC求乘积
  • Android学习初感觉
  • KM算法模板(二分图的最大权匹配)
  • 《社交红利》读书总结--如何从微信微博QQ空间等社交网络带走海量用户、流量与收入...
  • 三分法求凸性函数极大极小值
  • CodeForces 612D
  • 【leetcode】Factorial Trailing Zeroes(easy)
  • 数学知识小记
  • Fabric架构演变之路
  • HTTP--网络协议分层,http历史(二)
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • k8s如何管理Pod
  • MaxCompute访问TableStore(OTS) 数据
  • NSTimer学习笔记
  • Python 基础起步 (十) 什么叫函数?
  • ubuntu 下nginx安装 并支持https协议
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • 百度小程序遇到的问题
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 观察者模式实现非直接耦合
  • 漫谈开发设计中的一些“原则”及“设计哲学”
  • 腾讯优测优分享 | 你是否体验过Android手机插入耳机后仍外放的尴尬?
  • 一个完整Java Web项目背后的密码
  • #NOIP 2014# day.2 T2 寻找道路
  • (1)Nginx简介和安装教程
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (二)什么是Vite——Vite 和 Webpack 区别(冷启动)
  • (翻译)terry crowley: 写给程序员
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (四)模仿学习-完成后台管理页面查询
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (转)Google的Objective-C编码规范
  • (转贴)用VML开发工作流设计器 UCML.NET工作流管理系统
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .NET 的程序集加载上下文
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .NET6使用MiniExcel根据数据源横向导出头部标题及数据
  • .NET企业级应用架构设计系列之开场白
  • .NET正则基础之——正则委托
  • @for /l %i in (1,1,10) do md %i 批处理自动建立目录
  • @TableLogic注解说明,以及对增删改查的影响
  • @基于大模型的旅游路线推荐方案
  • []指针
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [BJDCTF 2020]easy_md5
  • [C语言][PTA基础C基础题目集] strtok 函数的理解与应用