YbtOJ 「数学基础」 第3章 同余问题

同余问题

同余

整除和同余的关系:

  1. \(a\equiv b\ (mod\ m)\) 当且仅当\(m|(a-b)\)
  2. \(a\equiv b\ (mod\ m )\) 当且仅当\(a=b+k\times m\) (\(m\)为整数)

同余的性质:

  1. 同加性:\(a\equiv b\ (mod\ m)\) 那么 \(a+c\equiv b+c\ (mod\ m)\)

  2. 同乘性:\(a\equiv b\ (mod\ m)\) 那么 \(ac\equiv bc\ (mod\ m)\)

    一般情况下 如果\(a\equiv b\ (mod\ m)\)\(c\equiv d\ (mod\ m)\)\(ac\equiv bd\ (mod\ m)\)

    但同余不满足同除性

  3. 同幂性:\(a\equiv b\ (mod\ m)\) 那么$a^n\equiv b^n\ (mod\ m) $

  4. 若$a\equiv x\ (mod\ p) $ 且 $a\equiv x\ (mod\ q) $ 其中\(pq\)互质 则$a\equiv x\ (mod\ pq) $

费马小定理

如果\(p\)为质数 那么对于任意一个整数\(a\) 满足\(a^p\equiv a\ (mod\ p)\)

推论:当\(p\)为质数且\(p\)不是\(a\)的约数时 \(a^{p-1}\equiv 1\ (mod\ p)\)

欧拉函数

性质:

  1. \(n=p^k\) 其中\(n,k\)为正整数 \(p\)为质数 那么\(\varphi (n)=p^k-p^{k-1}\)
  2. \(n,m\)为正整数 且\(gcd(n,m)=1\) 那么\(\varphi(n)=\varphi(n) \times \varphi(m)\)
  3. $ {\textstyle \prod_{i=1}^{n}} (1-\frac 1 {p_i})$ (\(p_i\)\(n\)唯一分解的底数(质数))

欧拉定理:

\(gcd(a,n)=1\)时 有\(a^{\varphi(n)} \equiv 1\ (mod\ n)\)

扩展欧几里得算法

扩欧证明及解法

image

同余方程证明及解法

image

void exgcd ( int a , int b , int &x , int &y )
{
	if ( !b ) { x = 1 , y = 0; return; }
	exgcd ( b , a % b , y , x );
	y -= ( a / b ) * x;
}

A. 【例题1】同余方程

对于\(ax\equiv 1\ ( mod\ b)\) 保证一定有解 那么\(a,b\)一定互质 所以用扩欧可以求出一组解 但这组解不一定是最小整数解

那么我们\(x\ mod\ b + b\)\(mod\ b\)即可(因为在模意义下一定有\(gcd(a,b)\)个解)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 1e8 + 1;
const int mod = 1e9 + 7;

int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

void exgcd ( int a , int b , int &x , int &y )
{
	if ( !b ) { x = 1 , y = 0; return; }
	exgcd ( b , a % b , y , x );
	y -= ( a / b ) * x;
	
}

int a , b , x , y;

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	a = read() , b = read();
	exgcd ( a , b , x , y );
	cout << ( x % b + b ) % b << endl;
	return 0;
	
}

B. 【例题2】约数之和

对于一个数\(n\),若\(n=p_{1}^{m_{1}} * p_{2}^{m_{2}} \ldots * p_{n}^{m_{n}}\) 。对于\(p_{i}\)在和中贡献 \(\sum_{j=0}^{m_{i}} p_{i}^{j}\) 。则约数和为 \(\prod_{i=1}^{n}\sum_{j=0}^{m_{i}} p_{i}^{j}\)

易对于 \(A^{B}\) 来说 \(A n s=\prod_{i=1}^{n}\sum_{j=0}^{B * m_{i}} p_{i}^{j}\)

累乘的原因:因为你第一组\(p_1^{random}\) 和第二组\(p_2^{random}\)中的每一个数都需都可以组成一个因数 那么使用乘法分配律就可以不重不漏地(因为是质数)求得最终的数量和

等比数列公式:

\((1)\): \(x=p^{0}+p^{1} \ldots p^{m}\)

\((2)\): \(p x=p^{1}+p^{2} \ldots p^{m+1}\)

\((2) - (1)\) 则得 $x=\frac{p_{i}^{B\ *\ m_{\ i}\ +\ 1}-1}{p_{i}-1} $ 。
\(A n s=\prod_{i=1}^{n}\left(\frac{p_{i}^{B\ *\ m_{\ i}\ +\ 1}-1}{p_{i}-1}\right)\)

分子可以用快速幂求 分母看是否与\(9901\)互质 如果不互质的话就是\(p_i-1\)的逆元 如果互质 那么\(p_i\ mod\ 9901=1\)

不互质时 需要用到一个结论:如果\(p_i\ mod\ x=b\) 那么\(p^{t}\ mod\ x=b^t\)

我们以二次方为例 设\(p=kx+b\) 那么\(p^2=k^2x^2+2kbx+b^2\)

所以在\(mod\ x\)意义下 \(p^{2}\ mod\ x=b^2\)

所以$p_i^{random} mod\ 9901=1 $

此时分母在模意义下为0 因为这个分数为整数 那么分子在模意义下也为0 那么我们用\(\sum_{j=0}^{B * m_{i}} p_{i}^{j}=B*m_i+1\)来计入答案

注意:实现时需要\(+mod\%mod\)

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 5e7 + 5;
const int mod = 9901;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}
		
int a , b , n , m , c[N] , prime[N] , cnt , ans = 1;
		
int ksm ( int x , int k )
{
	int base = x , res = 1;
	while (k) 
	{
		if ( k & 1 ) res = res * base % mod;
		base = base * base % mod;
		k >>= 1;
	}
	return res;
}
		
void solve ( int x )
{
	for ( int i = 2 ; i * i <= x ; i ++ )
		if ( x % i == 0 )
		{
			prime[++cnt] = i;
			while ( x % i == 0 )
			{
				c[cnt] ++;
				x /= i;
			}
		}
	if ( x > 1 )
	{
		prime[++cnt] = x;
		c[cnt] ++;
	}
}
		
int inv ( int x ) { return ksm ( x , mod - 2 ); }
		
signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	a = read() , b = read();
	solve ( a );
	for ( int i = 1 ; i <= cnt ; i ++ )
	{
		if ( ( prime[i] - 1 ) % mod == 0 ) { ans = ans * ( b * c[i] + 1 ) % mod; continue; }
		int mu = inv ( prime[i] - 1 );
		int zi = ( ksm ( prime[i] , b * c[i] + 1 ) + mod - 1 ) % mod;
		ans = ans * zi * mu % mod;
	}
	cout << ( ans + mod ) % mod << endl;
	return 0;
}

逆元

\(ax≡1(mod\ b)\)\(x\)的最小整数解 即为\(a\)\(mod\ b\) 意义下的逆元。

通俗讲,逆元(\(x^{-1}\))j即为\(a\)\(mod\ b\) 下的倒数

求逆元方法:

  1. 根据费马小定理 \(a\times a^{p-2}\equiv 1\ (mod\ p)\)

    所以可以得知\(a^{p-2}\)即为\(a\)\(mod\ p\)意义下的逆元

    int ksm ( int x , int k )
    {
    	int base = x , res = 1;
    	while (k) 
    	{
    		if ( k & 1 ) res = res * base % mod;
    		base = base * base % mod;
    		k >>= 1;
    	}
    	return res;
    }
    int inv ( int x ) { return ksm ( x , mod - 2 ); }
    
  2. \(exgcd\)求解

    void exgcd ( int aa , int bb , int &x , int &y )
    {
    	if ( !bb ) return x = 1 , y = 0 , void();
    	exgcd ( bb , aa % bb , y , x );
    	y -= aa / bb * x;
    }
    int inv ( int numm , int modd )
    {
    	int x , y;
    	exgcd ( numm , modd , x , y );
    	return ( x % modd + modd ) % modd;
    }
    
  3. 线性方法

    线性求逆元: 以\(O(n)\)复杂度求\(1-n\)的逆元

    设当前数为\(x\), 设模数\(m=\left\lfloor\frac{m}{x}\right\rfloor \cdot x+r\) 则必有\(x>r\) .

    将上式转化为\(m=\left\lfloor\frac{m}{x}\right\rfloor \cdot x+r\equiv 0(\bmod m)\)

    两边同乘 \(x^{-1} \cdot r^{-1}:\left\lfloor\frac{m}{x}\right\rfloor \cdot r^{-1}+x^{-1} \equiv 0(\bmod m)\)

    移项得$ x^{-1}=\left\lfloor\frac{m}{x}\right\rfloor \cdot\left(-r^{-1}\right) \bmod m$

    由于\(x>r\)\(r^{-1}\)已知 则\(x^{-1}\)可求

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

C. 【例题3】线性求逆元

模板题

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'

#define int long long 

const int N = 3e6 + 5;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int a[N] , n , m , inv[N];

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read() , m = read();
	inv[1] = 1;
	for ( int i = 2 ; i <= n ; i ++ ) inv[i] = ( ( m - m / i ) * inv[m%i] + m ) % m;
	for ( int i = 1 ; i <= n ; i ++ ) cout << inv[i] << endl;
	return 0;
}

中国剩余定理

对于很多组\(x\equiv a_i\ (mod\ m_i)\) 其中\(m_i\)为两两互质的质数 我们的目标是找出\(x\)的最小整数解

我们设\(M\)\(m_1\)\(m_n\)的最小公倍数 那么\(M=\prod_{i=1}^n m_i\)

\(k_i=M/m_i\) 并找到\(k_i\)的逆元\(b_i\) 那么最小解即为\(\prod_{i=1}^nm_ik_ib_i\ (mod\ M)\)

证明:(我们要试着构造出这个解)

对于\(x\equiv a_i\ (mod\ m_i)\) 我们设\(x=a_i\) 显然成立 但是这个解只满足这一个方程 但不一定满足其他的同余方程

所以我两边同乘\(\frac {M}{m_i}\)\(a_i\frac {M}{m_i}\equiv a_i\frac {M}{m_i}(mod\ m_i)\)(同余的同乘性)

所以当\(x=a_i\frac {M}{m_i}\)时 除了第\(i\)个方程 所有的\(1-n\)个方程都满足\(x\equiv 0(mod\ m_i)\) 但是我们为了好算 两面同乘\((\frac{M}{m_i})^{-1}\)

所以当\(x=a_i\frac {M}{m_i}(\frac{M}{m_i})^{-1}\)时 对于第\(i\)个方程 \(x\equiv a_i(mod\ b)\) 除了第\(i\)个方程的所有方程都满足\(x\equiv 0(mod\ b)\)

那么我们将所有这样的\(a_i\frac {M}{m_i}(\frac{M}{m_i})^{-1}\)加起来即可 最终答案就是\(\sum_{i=1}^na_i\frac {M}{m_i}(\frac{M}{m_i})^{-1} mod\ M\)

根据同余的同加性 一定可以满足所有方程 再\(mod\ M\)得到一个最小解

\(p.s.\) \(mod\ M\)的原因:对于每一个方程 你成倍地加上或减去\(M\) 对同余性是没有影响的(因为\(M\)一定是模数的倍数 所以消掉了)

D. 【例题4】中国剩余定理

模板题 解法见上

观察题解可知这里费马小定理失效(因为模数虽然互质 但是不一定保证是质数) 所以需要用\(exgcd\)求解逆元

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long 
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f3f3f3f3f;
int read ()
{
	int x = 0 , f = 1;
	char ch = cin.get();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
	return x * f;
}

int n , a[N] , b[N] , ans = 0 , M = 1 , k[N] , r[N];

//int ksm ( int x , int k , int mod )
//{
//	int ans = 1 , base = x;
//	while (k)
//	{
//		if ( k & 1 ) ans = ans * base % mod;
//		base = base * base % mod;
//		k >>= 1;
//	}
//	return ans;
//}
//
//int inv ( int x , int mm ) { return ksm ( x , mm - 2 , mm ); } 

void exgcd ( int aa , int bb , int &x , int &y )
{
	if ( !bb ) return x = 1 , y = 0 , void();
	exgcd ( bb , aa % bb , y , x );
	y -= aa / bb * x;
}
int inv ( int numm , int modd )
{
	int x , y;
	exgcd ( numm , modd , x , y );
	return ( x % modd + modd ) % modd;
}

signed main ()
{
	ios::sync_with_stdio(false);
	cin.tie(0) , cout.tie(0);
	n = read();
	for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , b[i] = read() , M *= a[i];//模数 余数
	for ( int i = 1 ; i <= n ; i ++ ) k[i] = M / a[i];
	for ( int i = 1 ; i <= n ; i ++ )
		ans = ( ( ans + b[i] * k[i] * inv ( k[i] , a[i] ) ) % M + M ) % M;
	cout << ans % M << endl;
	return 0;
}

posted @ 2023-06-28 16:36  Echo_Long  阅读(239)  评论(0)    收藏  举报