前言
在考试中,经常会出现一类问题,它们不涉及很深的算法,却和数学息息相关。这样的问题通常难度不大,也不需要特别的数学知识,只要掌握简单的数理逻辑即可。——《算法笔记》胡凡
质数(素数)
质数又称素数,是指除了1和本身外,不能被其它的任何数整除。
质数的判定
如何判断给定的正整数n是否是质数?
时间复杂度:O ( s q r t ( n ) ) O(sqrt(n)) O ( s q r t ( n ))
1 2 3 4 5 6 7 8 bool isPrime (int n) { if (n < 2 ) return false ; for (int i = 2 ; i <= n / i; i++) if (n % i == 0 ) return false ; return true ; }
分解质因数
n
中有且仅有一个大于sqrt(n)
的质因子。(如果有两个,两者相乘将大于n
,明显不符合)
据此,我们可以先枚举小于sqrt(n)
的所有质因子
如果最后n
仍大于1,那么这个n
就是那个大于sqrt(n)
的质因子
最坏时间复杂度:O ( s q r t ( n ) ) O(sqrt(n)) O ( s q r t ( n ))
最好时间复杂度:O ( l o g ( n ) ) O(log(n)) O ( l o g ( n )) ,(若n
为2 k 2^k 2 k ,那么在i=2
时,所有因子就被分解出来了,所以时间应该是l o g ( 2 k ) = k log(2^k)=k l o g ( 2 k ) = k )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void divide (int n) { for (int i = 2 ; i <= n / i; i++) if (n % i == 0 ) { int s = 0 ; while (n % i == 0 ) { s++; n /= i; } printf ("%d %d\n" , i, s); } if (n > 1 ) printf ("%d %d" , n, 1 ); }
筛质数
筛选出从1-n
之间质数的个数
数据量小于等于1 0 6 10^6 1 0 6 时,以下两个算法效率不相上下
埃氏筛法思想
一个数是p
,如果2~p-1
中的质数都不是它的质因数的话,那么p
是质数(以此对算法进行优化)
朴素筛法
O ( n l o g ( n ) ) O(nlog(n)) O ( n l o g ( n ))
时间复杂度计算,当n=2时,执行n/2次;当n=3时,执行n/3次
n 2 + n 3 + n 4 + . . . + n n \frac{n}{2}+\frac{n}{3}+\frac{n}{4}+...+\frac{n}{n} 2 n + 3 n + 4 n + ... + n n
= n ∗ ( 1 2 + 1 3 + 1 4 + . . . + 1 n ) =n*(\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}) = n ∗ ( 2 1 + 3 1 + 4 1 + ... + n 1 )
= n ∗ lim x → + ∞ 1 2 + 1 3 + 1 4 + . . . + 1 n =n*\lim_{x \to +\infty}{\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}} = n ∗ lim x → + ∞ 2 1 + 3 1 + 4 1 + ... + n 1
= n ∗ l n ( n ) < n ∗ l o g ( n ) = n l o g ( n ) =n*ln(n)<n*log(n)=nlog(n) = n ∗ l n ( n ) < n ∗ l o g ( n ) = n l o g ( n )
1 2 3 4 5 6 7 8 9 10 11 12 int primes[N];bool st[N];void get_primes (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]) primes[cnt++] = i; for (int j = i + i; j <= n; j += i) st[j] = true ; } } cout << cnt << endl ;
优化
O ( n l o g ( l o g ( n ) ) ) O(nlog(log(n))) O ( n l o g ( l o g ( n ))) 。相当于线性的时间复杂度
1~n
中有n l n ( n ) \frac{n}{ln(n)} l n ( n ) n 个质数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int primes[N];bool st[N];void get_primes (int n) { for (int i = 2 ; i <= n; i++) { if (!st[i]) { primes[cnt++] = i; for (int j = i + i; j <= n; j += i) st[j] = true ; } } } cout << cnt << endl ;
线性筛法(*常用)
n
只会被最小质因子 筛掉
i % primes[j] == 0
p r i m e s [ j ] primes[j] p r im es [ j ] 一定是i i i 的最小质因子,p r i m e s [ j ] primes[j] p r im es [ j ] 一定是p r i m e s [ j ] ∗ i primes[j]*i p r im es [ j ] ∗ i 的最小质因子
i % primes[j] != 0
p r i m e s [ j ] primes[j] p r im es [ j ] 一定小于i i i 的所有质因子,p r i m e s [ j ] primes[j] p r im es [ j ] 也一定是p r i m e s [ j ] ∗ i primes[j]*i p r im es [ j ] ∗ i 的最小质因子
1 2 3 4 5 6 7 8 9 10 11 12 int primes[N]; bool st[N]; void get_primes (int n ) { for (int i = 2 ; i <= n; i++) { if (!st[i]) primes[cnt++] = i; for (int j = 0 ; primes[j] <= n / i; j++) { st[primes[j] * i] = true ; if (i % primes[j] == 0 ) break ; } } }
约数
12的约数为1,12,3,4,2,6
试除法求约数
O ( s q r t ( n ) ) O(sqrt(n)) O ( s q r t ( n ))
1 2 3 4 5 6 7 8 9 10 11 12 vector <int > get_divisors (int n) { vector <int > res; for (int i = 1 ; i <= n / i; i++) if (n % i == 0 ) { res.push_back(i); if (i != n / i) res.push_back(n / i); } sort(res.begin(), res.end()); return res; }
约数个数
若要求出N
的约数个数,首先将N
质因数分解,p
代表质数,α \alpha α 为p
的次数
N = p 1 α 1 + p 2 α 2 + p 3 α 3 + . . . + p n α n N=p_1^{\alpha_1}+p_2^{\alpha_2}+p_3^{\alpha_3}+...+p_n^{\alpha_n} N = p 1 α 1 + p 2 α 2 + p 3 α 3 + ... + p n α n
然后求出每个质因数的约数个数,如
p 1 α 1 : p 1 0 , p 1 1 , . . . , p 1 α 1 − 1 , p 1 α 1 p_1^{\alpha_1}:p_1^0,p_1^1,...,p_1^{\alpha_1-1},p_1^{\alpha_1} p 1 α 1 : p 1 0 , p 1 1 , ... , p 1 α 1 − 1 , p 1 α 1
所以p 1 α 1 p_1^{\alpha_1} p 1 α 1 共α 1 + 1 \alpha_1+1 α 1 + 1 个约数,同理p 2 α 2 p_2^{\alpha_2} p 2 α 2 共α 2 + 1 \alpha_2+1 α 2 + 1 个约数……
根据乘法原则,N的约数个数为( α 1 + 1 ) ( α 2 + 1 ) ∗ . . . ∗ ( α n + 1 ) (\alpha_1+1)(\alpha_2+1)*...*(\alpha_n+1) ( α 1 + 1 ) ( α 2 + 1 ) ∗ ... ∗ ( α n + 1 )
约定n n n 个整数a i a_i a i ,请你输出这些数的乘积的约数个数,答案对1 0 9 + 7 10^9+7 1 0 9 + 7 取模
数据范围
1 ≤ n ≤ 100 1 \le n \le 100 1 ≤ n ≤ 100
1 ≤ a i ≤ 2 ∗ 1 0 9 1 \le a_i \le 2*10^9 1 ≤ a i ≤ 2 ∗ 1 0 9
输入格式
输出格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <algorithm> #include <unordered_map> using namespace std ;typedef long long LL;const int mod = 1e9 + 7 ;int n;int main () { cin >> n; unordered_map <int , int > primes; while (n -- ) { int x; cin >> x; for (int i = 2 ; i <= x/i; i++) while (x % i == 0 ) { x /= i; primes[i]++; } if (x > 1 ) primes[x]++; } LL res = 1 ; for (auto prime: primes) res = res * (prime.second + 1 ) % mod; cout << res << endl ; return 0 ; }
约数之和
若要求出N
的约数之和,首先将N
质因数分解,p
代表质数,α \alpha α 为p
的次数
N = p 1 α 1 + p 2 α 2 + p 3 α 3 + . . . + p n α n N=p_1^{\alpha_1}+p_2^{\alpha_2}+p_3^{\alpha_3}+...+p_n^{\alpha_n} N = p 1 α 1 + p 2 α 2 + p 3 α 3 + ... + p n α n
约数之和公式为
( p 1 0 + p 1 1 + . . . + p 1 α 1 ) ( p 2 0 + p 2 1 + . . . + p 2 α 2 ) . . . ( p n 0 + p n 1 + . . . + p n α n ) (p_1^0+p_1^1+...+p_1^{\alpha_1})(p_2^0+p_2^1+...+p_2^{\alpha_2})...(p_n^0+p_n^1+...+p_n^{\alpha_n}) ( p 1 0 + p 1 1 + ... + p 1 α 1 ) ( p 2 0 + p 2 1 + ... + p 2 α 2 ) ... ( p n 0 + p n 1 + ... + p n α n )
至于为什么是这样计算呢?可以利用乘法分配律进行验证,两两相乘后可以得到N
的所有约数(因为这些质因数本就是由约数拆解而来,这步相当于还原了用质数还原了约数),进行加和就是答案
约定n n n 个整数a i a_i a i ,请你输出这些数的乘积的约数之和,答案对1 0 9 + 7 10^9+7 1 0 9 + 7 取模
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <iostream> #include <algorithm> #include <unordered_map> using namespace std ;typedef long long LL;const int mod = 1e9 + 7 ;int n;int main () { cin >> n; unordered_map <int ,int > primes; while (n -- ) { int x; cin >> x; for (int i = 2 ; i <= x/i; i++) while (x % i == 0 ) { x /= i; primes[i]++; } if (x > 1 ) primes[x]++; } LL res = 1 ; for (auto prime: primes) { int p = prime.first, a = prime.second; LL t = 1 ; while (a--) t = (t * p + 1 ) % mod; res = (res * t) % mod; } cout << res << endl ; return 0 ; }
最大公约数
欧几里得算法,即辗转相除法,是将两个数辗转相除直到余数为0
O ( l o g ( n ) ) O(log(n)) O ( l o g ( n ))
1 2 3 int gcd (int a, int b) { return b? gcd(b, a % b):a; }
欧拉函数
在数论,对正整数n,欧拉函数是小于等于n的正整数中与n互质 的数的数目
互质:若N个整数的最大公因数 是1,则称这N个整数互质。
若要求出N
的欧拉函数,首先将N
质因数分解,p
代表质数,α \alpha α 为p
的次数
N = p 1 α 1 + p 2 α 2 + p 3 α 3 + . . . + p n α n N=p_1^{\alpha_1}+p_2^{\alpha_2}+p_3^{\alpha_3}+...+p_n^{\alpha_n} N = p 1 α 1 + p 2 α 2 + p 3 α 3 + ... + p n α n
那么欧拉函数公式如下:
ϕ ( n ) = N ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) \phi(n)=N*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...*(1-\frac{1}{p_k}) ϕ ( n ) = N ∗ ( 1 − p 1 1 ) ∗ ( 1 − p 2 1 ) ∗ ... ∗ ( 1 − p k 1 )
证明如下(容斥原理):
N同时代表个数,首先我们减去N中所有质因数倍数的个数
N − N p 1 − N p 2 − . . . − N p k N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k} N − p 1 N − p 2 N − ... − p k N
但这样减会出现一个问题,有的数同时是p1、p2的倍数,就会被减去两次,所以我们需要加回来
N − N p 1 − N p 2 − . . . − N p k + N p 1 ∗ p 2 + N p 2 ∗ p 3 + . . . N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k}+\frac{N}{p_1*p_2}+\frac{N}{p_2*p_3}+... N − p 1 N − p 2 N − ... − p k N + p 1 ∗ p 2 N + p 2 ∗ p 3 N + ...
一些数同时是p1、p2、p3的倍数,那么减去3次的同时,又加上了3次,相当于没有计算,因此
N − N p 1 − N p 2 − . . . − N p k + N p 1 ∗ p 2 + N p 2 ∗ p 3 + . . . − N p 1 ∗ p 2 ∗ p 3 − N p 2 ∗ p 3 ∗ p 4 N-\frac{N}{p_1}-\frac{N}{p_2}-...-\frac{N}{p_k}+\frac{N}{p_1*p_2}+\frac{N}{p_2*p_3}+...-\frac{N}{p_1*p_2*p_3}-\frac{N}{p_2*p_3*p_4} N − p 1 N − p 2 N − ... − p k N + p 1 ∗ p 2 N + p 2 ∗ p 3 N + ... − p 1 ∗ p 2 ∗ p 3 N − p 2 ∗ p 3 ∗ p 4 N
根据以上式子,可以看出一定的规律,将它们进行整理即可得到欧拉函数
求一个数的欧拉函数
1 2 3 4 5 6 7 8 9 10 11 12 13 int phi (int x) { int res = x; for (int i = 2 ; i <= x / i; i ++ ) if (x % i == 0 ) { res = res / i * (i - 1 ); while (x % i == 0 ) x /= i; } if (x > 1 ) res = res / x * (x - 1 ); return res; }
筛选法求欧拉
给定一个数n
,求出1-n
中每个数欧拉函数的和。这里使用到了前面线性筛质数的方法
若i
为质数,那么i
的欧拉函数ϕ ( i ) \phi(i) ϕ ( i ) 为i-1
若i
不为质数,求ϕ ( p r i m e s [ j ] ∗ i ) \phi(primes[j]*i) ϕ ( p r im es [ j ] ∗ i ) 分两种情况
若i % primes[j] == 0
,意味着primes[j]
是i
的一个质因子
此时ϕ ( i ) = i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) \phi(i)=i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...*(1-\frac{1}{p_k}) ϕ ( i ) = i ∗ ( 1 − p 1 1 ) ∗ ( 1 − p 2 1 ) ∗ ... ∗ ( 1 − p k 1 )
则ϕ ( p r i m e s [ j ] ∗ i ) = p r i m e s [ j ] ∗ i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) \phi(primes[j]*i)=primes[j]*i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...*(1-\frac{1}{p_k}) ϕ ( p r im es [ j ] ∗ i ) = p r im es [ j ] ∗ i ∗ ( 1 − p 1 1 ) ∗ ( 1 − p 2 1 ) ∗ ... ∗ ( 1 − p k 1 )
对比两个式子发现,后者只比前者多乘了一个p r i m e s [ j ] primes[j] p r im es [ j ] ,因此我们得到公式为
ϕ ( p r i m e s [ j ] ∗ i ) = p r i m e s [ j ] ∗ ϕ ( i ) \phi(primes[j]*i)=primes[j]*\phi(i) ϕ ( p r im es [ j ] ∗ i ) = p r im es [ j ] ∗ ϕ ( i )
若i % primes[j] != 0
,意味着意味着primes[j]
不是i
的一个质因子
此时ϕ ( i ) = i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) \phi(i)=i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...*(1-\frac{1}{p_k}) ϕ ( i ) = i ∗ ( 1 − p 1 1 ) ∗ ( 1 − p 2 1 ) ∗ ... ∗ ( 1 − p k 1 )
则ϕ ( p r i m e s [ j ] ∗ i ) = p r i m e s [ j ] ∗ i ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ . . . ∗ ( 1 − 1 p k ) ∗ ( 1 − 1 p j ) \phi(primes[j]*i)=primes[j]*i*(1-\frac{1}{p_1})*(1-\frac{1}{p_2})*...*(1-\frac{1}{p_k})*(1-\frac{1}{p_j}) ϕ ( p r im es [ j ] ∗ i ) = p r im es [ j ] ∗ i ∗ ( 1 − p 1 1 ) ∗ ( 1 − p 2 1 ) ∗ ... ∗ ( 1 − p k 1 ) ∗ ( 1 − p j 1 )
对比两个式子,因为此时的primes[j]
不是i
的一个质因子,所以在求primes[j]*i
时,需要比原先式子多乘了primes[j]
和(1-1/pj)
,化简得最后结果如下
ϕ ( p r i m e s [ j ] ∗ i ) = p [ j ] ∗ ϕ ( i ) ∗ ( 1 − 1 p j ) = ϕ ( i ) ∗ ( p j − 1 ) \phi(primes[j]*i)=p[j]*\phi(i)*(1-\frac{1}{p_j})=\phi(i)*(p_j-1) ϕ ( p r im es [ j ] ∗ i ) = p [ j ] ∗ ϕ ( i ) ∗ ( 1 − p j 1 ) = ϕ ( i ) ∗ ( p j − 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int get_eulers (int n) { phi[1 ] = 1 ; for (int i = 2 ; i <= n; i++) { if (!st[i]) { primes[cnt++] = i; phi[i] = i - 1 ; } for (int j = 0 ; primes[j] <= n / i; j++) { st[primes[j]*i] = true ; if (i % primes[j] == 0 ) { phi[i*primes[j]] = primes[j] * phi[i]; break ; } phi[i*primes[j]] = (primes[j]-1 ) * phi[i]; } } LL res = 0 ; for (int i = 1 ; i <= n; i++) res += phi[i]; return res; }
欧拉数论定理
内容
若a a a 与n n n 互质(gcd(a,n)=1
),则$a^{\phi(n)} \equiv 1 ; mod ; n $
其中ϕ ( n ) \phi(n) ϕ ( n ) 称为对模n n n 缩系的元素个数
当n为质数时,$a^{n-1} \equiv 1 ; mod ; n $(费马小定理 )
证明
1~n
中,与n
互质的数为a 1 , a 2 , a 3 , . . . , a ϕ ( n ) a_1,a_2,a_3,...,a_{\phi(n)} a 1 , a 2 , a 3 , ... , a ϕ ( n ) (注意这里的a与前面的a不同含义 )
将上述的数同时乘以a
,得
a a 1 , a a 2 , a a 3 , . . . , a a ϕ ( n ) aa_1,aa_2,aa_3,...,aa_{\phi(n)} a a 1 , a a 2 , a a 3 , ... , a a ϕ ( n )
因a1,a2,a3
与n
互质并且a
与n
也互质,所以两个数的乘积也和n
互质。将两组分别内部同时相乘,得
a 1 ∗ a 2 ∗ a 3 ∗ . . . ∗ a ϕ ( n ) ≡ a a 1 ∗ a a 2 ∗ a a 3 ∗ . . . ∗ a a ϕ ( n ) a_1*a_2*a_3*...*a_{\phi(n)} \equiv aa_1*aa_2*aa_3*...*aa_{\phi(n)} a 1 ∗ a 2 ∗ a 3 ∗ ... ∗ a ϕ ( n ) ≡ a a 1 ∗ a a 2 ∗ a a 3 ∗ ... ∗ a a ϕ ( n )
a 1 ∗ a 2 ∗ a 3 ∗ . . . ∗ a ϕ ( n ) ≡ a ϕ ( n ) ( a 1 ∗ a 2 ∗ a 3 ∗ . . . ∗ a ϕ ( n ) ) a_1*a_2*a_3*...*a_{\phi(n)} \equiv a^{\phi(n)}(a_1*a_2*a_3*...*a_{\phi(n)}) a 1 ∗ a 2 ∗ a 3 ∗ ... ∗ a ϕ ( n ) ≡ a ϕ ( n ) ( a 1 ∗ a 2 ∗ a 3 ∗ ... ∗ a ϕ ( n ) )
约去相同部分,得
a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi(n)} \equiv 1 (mod \;n) a ϕ ( n ) ≡ 1 ( m o d n )
因此我们得出结论
$a^{\phi(n)} \equiv 1 \ mod \ n $
应用
这个定理可以用来简化幂的模运算。比如计算7 222 7^{222} 7 222 的个位数,实际是求7 222 7^{222} 7 222 被10
除的余数。7
和10
互质 ,且φ(10)=4。由欧拉定理知7 4 ≡ 1 ( m o d 10 ) 7^4\equiv 1\ (mod\ 10) 7 4 ≡ 1 ( m o d 10 ) 。所以7 222 = ( 7 4 ) 55 ∗ 7 2 ≡ 49 ≡ 9 ( m o d 10 ) 7^{222}=(7^{4})^{55}*7^2\equiv 49\equiv 9\ (mod 10) 7 222 = ( 7 4 ) 55 ∗ 7 2 ≡ 49 ≡ 9 ( m o d 10 )
快速幂
1 ≤ a , k , p ≤ 1 0 9 1 \le a,k,p \le 10^9 1 ≤ a , k , p ≤ 1 0 9
以l o g ( k ) log(k) l o g ( k ) 的时间复杂度,快速求出a k m o d p a^k \ mod\ p a k m o d p 的结果
要快速求出上述的结果,我们需要先预处理出a 2 0 , a 2 1 . . . ( m o d p ) a^{2^0},a^{2^1}...(mod\ p) a 2 0 , a 2 1 ... ( m o d p ) 的值
然后将a k a^k a k 进行二进制分解,a k = a 2 0 ∗ a 2 1 ∗ . . . = a 2 0 + 2 1 + . . . ( m o d p ) a^k=a^{2^0}*a^{2^1}*...=a^{2^0+2^1+...}(mod\ p) a k = a 2 0 ∗ a 2 1 ∗ ... = a 2 0 + 2 1 + ... ( m o d p )
最后用原先预处理的结果,相乘取模得出答案
此算法主要的时间花在了预处理O ( l o g ( k ) ) O(log(k)) O ( l o g ( k )) 和k
的填二进制拆解O ( l o g ( k ) ) O(log(k)) O ( l o g ( k )) 上
虽然上述过程有一些复杂,但在最后的算法实现非常简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 int qmi (int a, int k, int p) { int res = 1 ; while (k) { if (k & 1 ) res = (LL)res * a % p; k >>= 1 ; a = (LL)a * a % p; } return res; }
扩展欧几里得算法
求出一组x i , y i x_i,y_i x i , y i ,使其满足a i ∗ x i + b i ∗ y i = g c d ( a i , b i ) a_i*x_i+b_i*y_i=gcd(a_i,b_i) a i ∗ x i + b i ∗ y i = g c d ( a i , b i )
推导如下:
b y + ( a m o d b ) x = d by+(a\ mod\ b)x=d b y + ( a m o d b ) x = d
⇒ b y + ( a − [ a b ] ∗ b ) x = d \Rightarrow by+(a-[\frac{a}{b}]*b)x=d ⇒ b y + ( a − [ b a ] ∗ b ) x = d
⇒ a x + b ( y − [ a b ] ∗ x ) = d \Rightarrow ax+b(y-[\frac{a}{b}]*x)=d ⇒ a x + b ( y − [ b a ] ∗ x ) = d
1 2 3 4 5 6 7 8 9 10 11 int exgcd (int a, int b, int & x, int & y) { if (!b) { x = 1 , y = 0 ; return a; } int d = exgcd(b, a%b, y, x); y -= a / b * x; return d; }
线性同余方程
题目描述
给定n n n 组数据a i , b i , m i a_i,b_i,m_i a i , b i , m i ,对于每组数求出一个x i x_i x i ,使其满足a i ∗ x i ≡ b i ( m o d m i ) a_i*x_i \equiv b_i(mod\ m_i) a i ∗ x i ≡ b i ( m o d m i )
推导
a i ∗ x i ≡ b i ( m o d m i ) a_i*x_i \equiv b_i(mod\ m_i) a i ∗ x i ≡ b i ( m o d m i )
⇒ a i ∗ x i / m i = y i . . . b i \Rightarrow a_i*x_i / m_i = y_i...b_i ⇒ a i ∗ x i / m i = y i ... b i
⇒ a i ∗ x i − m i ∗ y i = b i \Rightarrow a_i*x_i - m_i*y_i=b_i ⇒ a i ∗ x i − m i ∗ y i = b i
令y i ′ = y i y_i\prime = y_i y i ′ = y i
⇒ a ∗ x + m ∗ y ′ = b \Rightarrow a*x + m*y\prime=b ⇒ a ∗ x + m ∗ y ′ = b
如果b
不是最大公约数d
的倍数,那么一定无解
根据扩展欧几里得算法,对于任意的整数a
和m
,必定存在一组x
和y'
,使得ax+my'
等于gcd(a,m)
所以可以求得a ∗ x + m ∗ y ′ = d a*x + m* y\prime =d a ∗ x + m ∗ y ′ = d
⇒ a ∗ x ∗ b d + m ∗ y ′ ∗ b d = b \Rightarrow a*x*\frac{b}{d} + m*y\prime*\frac{b}{d}=b ⇒ a ∗ x ∗ d b + m ∗ y ′ ∗ d b = b
所以要求解的xi
=x*b/d
中国剩余定理
中国剩余定理(Chinese remainder theorem, CRT)
小学数学时大家都学过这样的问题:有一群人,3个人一组,剩1人,5个人一组,剩3人,10人一组,剩7人。
那么有多少人呢?CRT的出现就是用来解决上述问题
以上的问题可以看成一个线性同余方程组
CRT公式:x = ( ∑ i = 1 n a i ∗ M i − 1 ∗ M i ) m o d M x=(\sum_{i=1}^n a_i*M_i^{-1}*M_i)\ mod \ M x = ( ∑ i = 1 n a i ∗ M i − 1 ∗ M i ) m o d M
以上问题可以转换成以下的线性同余方程组表达
{ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) ⋯ ⋯ x ≡ a k ( m o d m k ) \begin{cases} x\equiv a_1(mod \ m_1)\\ x\equiv a_2(mod \ m_2) \\ \cdots \cdots \\ x\equiv a_k(mod \ m_k)\end{cases} ⎩ ⎨ ⎧ x ≡ a 1 ( m o d m 1 ) x ≡ a 2 ( m o d m 2 ) ⋯⋯ x ≡ a k ( m o d m k )
M = m 1 ∗ m 2 ∗ ⋯ ∗ m k M=m_1*m_2*\cdots*m_k M = m 1 ∗ m 2 ∗ ⋯ ∗ m k
M i = M / m i = ⋯ ∗ m i − 1 ∗ m i + 1 ∗ ⋯ M_i=M/m_i=\cdots*m_{i-1}*m_{i+1}*\cdots M i = M / m i = ⋯ ∗ m i − 1 ∗ m i + 1 ∗ ⋯
此外,还需要求出M i M_i M i 的逆元M i − 1 M_i^{-1} M i − 1 ,使其满足条件M i ∗ M i − 1 ≡ 1 ( m o d m i ) M_i*M_i^{-1} \equiv 1(mod \ m_i) M i ∗ M i − 1 ≡ 1 ( m o d m i )
而对于本组中的其它i
,满足条件M j ∗ M j − 1 ≡ 0 ( m o d m i ) M_j*M_j^{-1} \equiv 0(mod \ m_i) M j ∗ M j − 1 ≡ 0 ( m o d m i )
根据x = a i ∗ M i − 1 ∗ M i x=a_i*M_i^{-1}*M_i x = a i ∗ M i − 1 ∗ M i ,求出其中的一个值
对每一线性同余方程执行上述过程,最后将结果对M
取模,得出最后答案
举个例子,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
即,一个整数除以3余2,除以5余3,除以7余2,求这个整数。
M = 3 ∗ 5 ∗ 7 = 105 M=3*5*7=105 M = 3 ∗ 5 ∗ 7 = 105
首先求解x ≡ 2 ( m o d 3 ) x \equiv 2 (mod \ 3) x ≡ 2 ( m o d 3 )
M 1 = M / m 1 = 105 / 3 = 35 M_1=M/m_1=105/3=35 M 1 = M / m 1 = 105/3 = 35
M 1 − 1 ∗ 35 ≡ 1 ( m o d 3 ) M_1^{-1}*35 \equiv 1 (mod \ 3) M 1 − 1 ∗ 35 ≡ 1 ( m o d 3 ) ,此步利用扩展欧几里得算法,求出M 1 − 1 = 2 M_1^{-1} = 2 M 1 − 1 = 2
x = a i ∗ M i − 1 ∗ M i = 2 ∗ 2 ∗ 35 = 2 ∗ 70 = 140 x=a_i*M_i^{-1}*M_i=2*2*35=2*70=140 x = a i ∗ M i − 1 ∗ M i = 2 ∗ 2 ∗ 35 = 2 ∗ 70 = 140
同理,可以求出x ≡ 3 ( m o d 5 ) x \equiv 3 (mod \ 5) x ≡ 3 ( m o d 5 ) 和x ≡ 2 ( m o d 7 ) x \equiv 2 (mod \ 7) x ≡ 2 ( m o d 7 )
x = 15 ∗ 2 + 21 ∗ 3 + 70 ∗ 2 = 233 m o d M = 233 m o d 105 = 23 x=15*2+21*3+70*2=233 \ mod \ M = 233 \ mod \ 105 = 23 x = 15 ∗ 2 + 21 ∗ 3 + 70 ∗ 2 = 233 m o d M = 233 m o d 105 = 23
这个余数23就是合乎条件的最小数.
扩展CRT
根据题目所述,这里的a 1 , a 2 , ⋯ , a n a_1,a_2,\cdots,a_n a 1 , a 2 , ⋯ , a n 不满足两两互质的条件,所以需要重新推导CRT
{ x ≡ m 1 ( m o d a 1 ) x ≡ m 2 ( m o d a 2 ) \begin{cases} x\equiv m_1(mod \ a_1)\\ x\equiv m_2(mod \ a_2) \\ \end{cases} { x ≡ m 1 ( m o d a 1 ) x ≡ m 2 ( m o d a 2 )
化简得,
{ x = k 1 a 1 + m 1 x = k 2 a 2 + m 2 \begin{cases} x= k_1a_1+m_1\\ x= k_2a_2+m_2 \\ \end{cases} { x = k 1 a 1 + m 1 x = k 2 a 2 + m 2
所以k 1 a 1 + m 1 = k 2 a 2 + m 2 k_1a_1+m_1=k_2a_2+m_2 k 1 a 1 + m 1 = k 2 a 2 + m 2
⇒ k 1 a 1 − k 2 a 2 = m 2 − m 1 \Rightarrow k_1a_1-k_2a_2=m_2-m_1 ⇒ k 1 a 1 − k 2 a 2 = m 2 − m 1 ,这个式子有解则满足g c d ( a 1 , a 2 ) ∣ m 2 − m 1 gcd(a_1,a_2) | m_2-m_1 g c d ( a 1 , a 2 ) ∣ m 2 − m 1
运用扩展欧几里得算法,能够求出k1,k2的通解,非齐次=齐次通解+非齐次特解
{ k 1 = k 1 + k a 2 d k 2 = k 2 + k a 1 d \begin{cases} k_1= k_1+k\frac{a_2}{d}\\ k_2= k_2+k\frac{a_1}{d} \\ \end{cases} { k 1 = k 1 + k d a 2 k 2 = k 2 + k d a 1
x = k 1 a 1 + m 1 x=k_1a_1+m_1 x = k 1 a 1 + m 1
⇒ x = ( k 1 + k a 2 d ) a 1 + m 1 \Rightarrow x=(k_1+k\frac{a_2}{d})a_1+m_1 ⇒ x = ( k 1 + k d a 2 ) a 1 + m 1
⇒ x = a 1 k 1 + m 1 + k a 1 a 2 d \Rightarrow x=a_1k_1+m_1+k\frac{a_1a_2}{d} ⇒ x = a 1 k 1 + m 1 + k d a 1 a 2
其中a1a2/d
就是a1a2
的最小公倍数,因此
⇒ x = a 1 k 1 + m 1 + k [ a 1 a 2 ] \Rightarrow x=a_1k_1+m_1+k[a_1a_2] ⇒ x = a 1 k 1 + m 1 + k [ a 1 a 2 ]
通过对比最前面化简后的式子,我们可以把a 1 k 1 + m 1 a_1k_1+m_1 a 1 k 1 + m 1 看成m
,k [ a 1 a 2 ] k[a_1a_2] k [ a 1 a 2 ] 看成ka
⇒ x = x 0 + k a \Rightarrow x=x_0+ka ⇒ x = x 0 + ka
以这样的形式重复更新合并后来的线性同余方程,可以得到最终答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 #include <iostream> #include <cmath> using namespace std ;typedef long long LL;int n;LL exgcd (LL a,LL b, LL& x, LL& y) { if (!b) { x = 1 , y = 0 ; return a; } LL d = exgcd(b, a%b, y, x); y -= a / b * x; return d; } int main () { cin >> n; LL a1, m1; cin >> a1 >> m1; bool has_answer = true ; for (int i = 0 ; i < n-1 ; i ++ ) { LL a2, m2; cin >> a2 >> m2; LL k1, k2; LL d = exgcd(a1, a2, k1, k2); if ((m2-m1) % d) { has_answer = false ; break ; } k1 *= (m2 - m1)/d; LL t = a2 / d; k1 = (k1 % t + t) % t; m1 = a1*k1+m1; a1 = abs (a1 / d * a2); } if (has_answer) cout << (m1 % a1 + a1) % a1 << endl ; else puts ("-1" ); return 0 ; }
204.表达整数的奇怪方式
高斯消元
求解线性方程组
题目描述
输入一个包含n个方程和n个未知数的线性方程组。方程组的系数为实数,求解这个方程组
数据范围
1 ≤ n ≤ 100 1 \le n \le 100 1 ≤ n ≤ 100
步骤如下,
枚举每一列
找到该列中绝对值 最大的那个数所在的行
将该行移动到最上面(次上、次次上)
将该行的第一个数变为1
用这个数把下面行该列的数消成0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const int N = 110 ;const double eps = 1e-6 ;int n;double a[N][N];int gauss () { int r, c; for (r = 0 , c = 0 ; c < n; c++) { int t = r; for (int i = r; i < n; i++) if (fabs (a[i][c]) > fabs (a[t][c])) t = i; if (fabs (a[t][c]) < eps) continue ; for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; for (int i = r + 1 ; i < n; i++) if (fabs (a[i][c]) > eps) for (int j = n; j >= c; j--) { a[i][j] -= a[i][c] * a[r][j]; } r++; } if (r < n) { for (int i = r; i < n; i++) if (a[i][n] > eps) return 2 ; return 1 ; } for (int i = n-1 ; i >= 0 ; i--) { for (int j = i+1 ; j < n; j++) { a[i][n] -= a[j][n]*a[i][j]; } } return 0 ; }
组合计数
排列组合中的组合
组合I
给定两个整数a,b,请你输出C a b C_a^b C a b
根据递推 式C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} C a b = C a − 1 b + C a − 1 b − 1 ,时间复杂度n 2 n^2 n 2
数据范围
1 ≤ b ≤ a ≤ 2000 1 \le b \le a \le 2000 1 ≤ b ≤ a ≤ 2000
1 ≤ n ≤ 10000 1 \le n \le 10000 1 ≤ n ≤ 10000
1 2 3 4 5 6 7 8 9 const int N = 2010 , mod = 1e9 +10 ;int c[N][N];void init () { for (int i = 0 ; i < N; i++) for (int j = 0 ; j <= i; j++) if (!j) c[i][j] = 1 ; else c[i][j] = (c[i-1 ][j] + c[i-1 ][j-1 ]) % mod; }
组合II
预处理出阶乘 ,用infact(a!)
表示a!
的逆元(快速幂)
C a b = a ! ( a − b ) ! b ! = f a c t ( a ! ) ∗ i n f a c t ( b ! ) ∗ i n f a c t ( ( a − b ) ! ) C_a^b=\frac{a!}{(a-b)!b!}=fact(a!)*infact(b!)*infact((a-b)!) C a b = ( a − b )! b ! a ! = f a c t ( a !) ∗ in f a c t ( b !) ∗ in f a c t (( a − b )!) ,时间复杂度n l o g ( n ) nlog(n) n l o g ( n )
数据范围
1 ≤ b ≤ a ≤ 1 0 5 1 \le b \le a \le 10^5 1 ≤ b ≤ a ≤ 1 0 5
根据费马小定理,a n − 1 ≡ 1 m o d n a^{n-1} \equiv 1\ mod \ n a n − 1 ≡ 1 m o d n ,其中a
与n
互质,且n为质数
得a n ∗ a n − 2 ≡ m o d n a^n*a^{n-2} \equiv mod \ n a n ∗ a n − 2 ≡ m o d n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std ;typedef long long LL;const int N = 1e5 + 10 , mod = 1e9 + 7 ;int fact[N], infact[N];int qmi (int a, int k, int p) { int res = 1 ; while (k) { if (k & 1 )res = (LL)res * a % p; a = (LL)a * a % p; k >>= 1 ; } return res; } int main () { fact[0 ] = infact[0 ] = 1 ; for (int i = 1 ; i < N; i++) { fact[i] = (LL)fact[i-1 ] * i % mod; infact[i] = (LL)infact[i-1 ] * qmi(i, mod-2 , mod) % mod; } int n; scanf ("%d" , &n); while (n -- ) { int a, b; scanf ("%d%d" , &a, &b); printf ("%d\n" , (LL)fact[a] * infact[b] % mod * infact[a-b] % mod); } return 0 ; }
组合III
数据范围
1 ≤ b ≤ a ≤ 1 0 18 1 \le b \le a \le 10^{18} 1 ≤ b ≤ a ≤ 1 0 18
1 ≤ n ≤ 20 1 \le n \le 20 1 ≤ n ≤ 20
1 ≤ p ≤ 1 0 5 1 \le p \le 10^5 1 ≤ p ≤ 1 0 5
卢卡斯定理
C a b = C a m o d p b m o d p ∗ C a / p b / p ( m o d p ) C_a^b=C_{a \ mod \ p}^{b \ mod \ p}*C_{a / p}^{b / p}(mod \ p) C a b = C a m o d p b m o d p ∗ C a / p b / p ( m o d p )
参考资料
AcWing算法基础课,当你还在犹豫的时候,别人已经学完了