同余2:线性逆元和中国剩余定理的学习笔记

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/ha_ing/article/details/100054488

同余2:逆元和中国剩余定理的学习笔记


前言
上一次,我讲到了求解线性同余方程,并挖了一个线性求逆元的大坑,现在补上吧。

逆元

逆元的定义:若 a x 1 ( m o d b ) a * x\equiv 1 \pmod b ,且 a a b b 互质,(若不互质则不存在),则称 x x a a b b 意义下的逆元,记作 a 1 a^{-1}
逆元的求法:
单个:拓展欧几里得,上一篇已经讲过了。
批量:线性逆元。
假设求模 p p 的逆元。
具体如下:首先, 1 1 1 ( m o d p ) 1^{-1} \equiv 1 \pmod p
然后,我们设 p = k i + r p = k * i + r ,其中 k , i , r k,i,r 均为整数,且 r < k r < k ,其实 k = p i k = \lfloor { p \over i}\rfloor ,而 r = p % i r = p \% i
不难得到 p 0 ( m o d p ) p \equiv 0 \pmod p ,拆开得 k i + r 0 ( m o d p ) k * i + r \equiv 0 \pmod p ,两边乘以 i 1 r 1 i^{-1}*r^{-1} 得(在模 p p 意义下相等是成立的): k r 1 + i 1 0 ( m o d p ) k * r^{-1} + i^{-1} \equiv 0 \pmod p 得, i 1 k r 1 ( m o d p ) i^{-1} \equiv -k * r^{-1} \pmod p 也就是 i 1 p i ( p % i ) 1 ( m o d p ) i^{-1} \equiv - \lfloor { p \over i}\rfloor * ( p \% i)^{-1} \pmod p

不难得出代码:

inv[i] = -(p/i) * inv[p%i];

非常简洁, O ( n ) O(n) 的时间可得出答案。

求逆元还有用费马小定理的方法,但本蒟蒻不会 ,就不赘述了。

中国剩余定理

中国剩余定理是用来求线性同余方程组的解的,出自《孙子算经》中的题目:今有物不知其数,三三数之剩二(模3得2),五五数之剩三(模5得3),七七数之剩二(模7得2),问物几何?这个问题就是孙子问题,也就是中国剩余定理的由来了。
古人的口诀为:三人同行七十稀,五树梅花廿一支,七子团圆正半月,除百零五便得知。
翻译过来为:
第一步,找出5和7的公倍数中模3得1的最小数为70,找出3和7的公倍数中模5得3的最小数为21,找出3和5的公倍数中模7得1的最小数为15;
第二步,用70乘以2,21乘以3,15乘以2,得出和为233,不难发现,这个数模3得2,模5得3,模7得2;
第三步,算出3,5,7三个数的最小公倍数为105,将233除以105,得到余数为23,这个即为符合要求的最小正整数。
原问题用严谨的数学语言表述就是:
{ x 2 ( m o d 3 ) x 3 ( m o d 5 ) x 2 ( m o d 7 ) \begin{cases} x \equiv 2 \pmod {3} \\[1ex] x \equiv 3 \pmod {5} \\[1ex] x \equiv 2 \pmod {7} \end{cases}
通过上述方法的第一步,古人提示我们先考虑以下三个同余方程:
{ x 1 ( m o d 3 ) x 0 ( m o d 5 ) (1) x 0 ( m o d 7 ) \begin{cases} x \equiv 1 \pmod {3} \\[1ex] x \equiv 0 \pmod {5} & & & \text{(1)} \\[1ex] x \equiv 0 \pmod {7} \end{cases}
{ x 0 ( m o d 3 ) x 1 ( m o d 5 ) (2) x 0 ( m o d 7 ) \begin{cases} x \equiv 0 \pmod {3} \\[1ex] x \equiv 1 \pmod {5}&&& \text{(2)} \\[1ex] x \equiv 0 \pmod {7} \end{cases}
{ x 0 ( m o d 3 ) x 0 ( m o d 5 ) (3) x 1 ( m o d 7 ) \begin{cases} x \equiv 0 \pmod {3} \\[1ex] x \equiv 0 \pmod {5}&&& \text{(3)} \\[1ex] x \equiv 1 \pmod {7} \end{cases}
以第一个方程为研究对象,通过观察,不难发现第一组线性同余方程的解一定为5,7的公倍数,可写出一个公式为 35 x 1 ( m o d 3 ) 35*x \equiv 1 \pmod 3 ,这个公式用拓展欧几里得可求出最小正整数解为70,那么,求出它又有什么用呢?
此时,我们不妨设 1 0 = 70 0 \begin{matrix} & 1 \\ & 0 & = & 70 \\ & 0 \\ \end{matrix}
即表示第一个同余方程组的解,
0 1 = 21 0 \begin{matrix} & 0 \\ & 1 & = & 21 \\ & 0 \\ \end{matrix}
表示第二个同余方程组的解,
0 0 = 15 1 \begin{matrix} & 0 \\ & 0 & = & 15 \\ & 1 \\ \end{matrix}
表示第三个同余方程组的解。
2 3 2 \begin{matrix} & 2 \\ & 3 \\ & 2 \\ \end{matrix} 表示原方程组的解
不难得出这些奇怪的数列相加得出的解满足条件,在取模的情况下,两个数相加时,它们之间的余数和若不大于取模用的数,则它们的和的余数为原先两个数的余数和,即 a % p + b % p = ( a + b ) % p a \% p + b \% p =(a + b) \% p
最后得出以下式子:
2 1 0 0 3 = 2 0 + 3 1 + 2 0 = 2 70 + 3 21 + 2 15 23 ( m o d 105 ) 2 0 0 1 \begin{matrix} & 2 & & & & 1& &&&0&&&&0\\ & 3 & = & 2 & * & 0 & + & 3 & * & 1&+&2&*&0 & = 2 * 70 + 3 * 21 + 2 * 15 \equiv23 \pmod {105} \\ & 2& & & & 0 &&&&0&&&&1\\ \end{matrix}
将答案取模它们的最大公因数后,余数关系仍成立。
我们好像发现了什么,那就让我们从特殊推广到一般吧。
中国剩余定理的基本内容是(摘自《数学一本通》):设自然数 m 1 , m 2 , . . . , m r m_1,m_2,...,m_r 两两互素,并记 N = m 1 m 2 . . . m r N = m_1*m_2*...*m_r ,则同余方程组:

{ x b 1 ( m o d m 1 ) x b 2 ( m o d m 2 ) . . . . . . x b r ( m o d m r ) \begin{cases} x \equiv b_1\pmod {m_1} \\[1ex] x \equiv b_2\pmod {m_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv b_r\pmod {m_r} \end{cases} 在模 N N 同余的意义下有唯一解。
根据上述方法,考虑同余方程组
{ x 0 ( m o d m 1 ) . . . x 0 ( m o d m i 1 ) x 1 ( m o d m i ) x 0 ( m o d m i + 1 ) . . . x 0 ( m o d m r ) \begin{cases} x \equiv 0 \pmod {m_1} \\[1ex] ...\\[1ex] x \equiv 0 \pmod {m_{i - 1}} \\[1ex] x \equiv 1 \pmod {m_i} \\[1ex] x \equiv 0 \pmod {m_{i + 1}} \\[1ex] ...\\[1ex] x \equiv 0 \pmod {m_r} \end{cases}
其中 1 i r 1 \le i \le r
由于这些数两两互素,令 x i = ( N m i ) x_i = ({N \over m_i} ) ,因为所有m互素,保证求出的答案不会增大其它 m m 的余数,推出同余方程为 k i x i 1 ( m o d m i ) k_i*x_i \equiv 1 \pmod {m_i} ,最终解为 b i k i x i b_i * k_i * x_i 。将所有解加上并注意对 N N 取模即可。
板子
[TJOI2009]猜数字 https://www.luogu.org/problem/P3868
本题等同于求解 { x a 1 ( m o d b 1 ) x a 2 ( m o d b 2 ) . . . . . . x a k ( m o d b k ) \begin{cases} x \equiv a_1\pmod {b_1} \\[1ex] x \equiv a_2\pmod {b_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv a_k\pmod {b_k} \end{cases}
注意a可能为负,以及数据过大要用"快(gui)速乘"。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define N 13
using namespace std;
ll a[N] , b[N] , k , n;
inline void read( ll & res ) {
	res = 0;
	ll pd = 1;
	char aa = getchar();
	while ( aa < '0' || aa > '9' ) {
		if ( aa == '-' ) {
			pd = -pd;
		}
		aa = getchar();
	}
	while ( aa >= '0' && aa <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( aa - '0' );
		aa = getchar();
	}
	res *= pd;
	return;
}
inline ll mul( ll tem , ll t ) {
	ll ans = 0;
	while ( t ) {
		if ( t & 1 ) {
			ans = ( ans + tem ) % n;
		}
		tem = ( tem + tem ) % n;
		t >>= 1;
	}
	return ans;
}
inline ll exgcd( ll a , ll b , ll & x , ll & y ) {
	if ( !b ) {
		x = 1;
		y = 0;
		return a;
	}
	ll d = exgcd( b , a % b , x , y );
	ll tem = x;
	x = y;
	y = tem - a / b * y;
	return d;
}
inline ll china(){
	ll res = 0;
	for ( int i = 1 ; i <= k ; ++i ) {
		ll x , y;
		exgcd( n / b[i] , b[i] , x , y );
		res = ( res + mul( mul( x , n/b[i] ) % n , a[i] ) % n ) % n;
	}
	return( res % n + n ) % n;
}
int main () {
	n = 1;
	read(k);
	for ( int i = 1 ; i <= k ; ++i ) {
		read(a[i]);
	}
	for ( int i = 1 ; i <= k ; ++i ) {
		read(b[i]);
		n *= b[i];
		a[i] = ( a[i] % b[i] + b[i] ) % b[i];
	}
	printf("%lld",china());
	return 0;
}

扩展中国剩余定理

求解:
{ x b 1 ( m o d m 1 ) x b 2 ( m o d m 2 ) . . . . . . x b r ( m o d m r ) \begin{cases} x \equiv b_1\pmod {m_1} \\[1ex] x \equiv b_2\pmod {m_2} \\[1ex] ...\\[1ex] ...\\[1ex] x \equiv b_r\pmod {m_r} \end{cases}
问题和上面差不多,不过自然数 m 1 , m 2 , . . . , m r m_1,m_2,...,m_r 不再两两互素
这样上面的方法就不适用了,第一步的做法不能保证对一个数进行操作时,不会增大取模其它数的余数。
对于这一点我们考虑使用类似数学归纳法的做法,具体来说,共分为两点:
一,定义初始状态;
二,思考如何从当前状态转到下一个状态。
好吧,不玩深沉了,所谓定义初始状态,就是直接得出第一组的解,第一组的解就是 b 1 b_1
所谓从当前状态转到下一个状态,就是合并思想,将当前这一组的解与前面所有组的解建立联系;不过为了方便,我们先考虑合并其中两个组,设为: { x b i ( m o d m i ) x b j ( m o d m j ) \begin{cases} x \equiv b_i\pmod {m_i} \\[1ex] x \equiv b_j\pmod {m_j} \\[1ex] \end{cases} 不难看出上式可表示为: { x = m i k i + b i x = m j k j + b j \begin{cases} x = m_i * k_i + b_i \\[1ex] x = m_j * k_j + b_j \\[1ex] \end{cases}
所以 m i k i m j k j = b j b i m_i * k_i - m_j * k_j = b_j -b_i
a = m i a = m_i b = m j b = m_j c = b j b i c = b_j -b_i
用扩欧求解同余方程 a x c ( m o d b ) a * x&#x27; \equiv c \pmod {b}
不难得出 k i k_i 的一种解,即为 x c G C D a , b x&#x27; *{ c \over GCD(a,b)}
如果 c % G C D a , b ! = 0 c \% GCD(a,b) != 0
那么整个方程组无解。
z = m j G C D ( m i , m j ) z = {m_j \over GCD(m_i,m_j)} ,将得出的 k i k_i 经过计算 ( k i % z + z ) % z (k_i \%z+z)\%z 即可得到最小正整数解。
则两个公式的通解为 x = m i k i + b i x = m_i * k_i + b_i ,它可以对 L C M ( m i , m j ) LCM(m_i,m_j) 取模,不影响余数的大小。
如果是合并当前组与所有在当前组的前面的组, { x N ( m o d M ) x b i ( m o d m i ) \begin{cases} x \equiv N \pmod {M} \\[1ex] x \equiv b_i\pmod {m_i} \\[1ex] \end{cases} 其中 N N 表示前 i 1 i - 1 组的解,M表示为 L C M ( m 1 , m 2 , . . . , m i 1 ) LCM(m_1 , m_2 , ... , m_{i - 1})
N N 已经保存了所有前 i 1 i - 1 个的取余条件,所有满足 x N ( m o d M ) x \equiv N \pmod {M} 的数都能成为前 i 1 i - 1 个方程的解。此时,只要再满足第 i i 个方程,就可以得出前 i i 个方程的通解。
得出:
{ x = M k 1 + N x = m i k 2 + b i \begin{cases} x = M * k_1 + N \\[1ex] x = m_i * k_2 + b_i \\[1ex] \end{cases}
所以 M k 1 m i k 2 = b i N M * k_1 - m_i * k_2 = b_i -N
按照上面合并两个同余方程的方法,用扩欧不难得出答案。
按照这种方法,从1一直递推到 r r ,就可以得出结果。
板子
【模板】扩展中国剩余定理(EXCRT)https://www.luogu.org/problem/P4777
就是一道模板题,不过注意要用“快(gui)速乘”。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
ll aa[N] , bb[N];
ll n;
inline void read( ll & res ) {
	res = 0;
	ll pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline ll mul( ll a , ll b , ll mod ) {
	ll ans = 0;
	while ( b ) {
		if ( b & 1 ) {
			ans = ( ans + a ) % mod;
		}
		a = ( a + a ) % mod;
		b >>= 1;
	}
	return ans;
}
inline ll exgcd( ll a , ll b , ll & x , ll & y ) {
	if ( !b ) {
		x = 1;
		y = 0;
		return a;
	}
	ll d = exgcd( b , a % b , x , y );
	ll t = x;
	x = y;
	y = t - a / b * y;
	return d;
}
inline ll china() {
	ll ans = bb[1];
	ll m = aa[1];
	for ( int i = 2 ; i <= n ; ++i ) {
		ll a = m;
		ll b = aa[i];
		ll c = ( bb[i] - ans % b + b ) % b;//式子在模b后仍成立,这里是为了保证c是正整数 
		ll x , y;
		ll d = exgcd( a , b , x , y );
		if ( c % d != 0 ) {
			return -1;
		}
		ll tem = b/d;
		ll res = mul( x , c/d , tem );
		ans += res * m;
		m *= tem;
		ans = ( ans % m + m ) % m;
	}
	return ( ans % m + m ) % m;
}
int main () {
	read(n);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(aa[i]);
		read(bb[i]);
	}
	printf("%lld",china());
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/ha_ing/article/details/100054488
今日推荐