这个复习没有什么顺序的... 想到什么复习什么而已qwq
问题描述
测试一个正整数 p p 是否为素数,需要较快且准确的测试方法。( p ≤ 10 18 ) ( p ≤ 10 18 )
算法解决
费马小定理
首先需要了解 费马小定理 。
费马小定理:对于质数 p p 和任意整数 a a ,有 a p ≡ a ( mod p ) a p ≡ a ( mod p ) 。
反之,若满足 a p ≡ a ( mod p ) a p ≡ a ( mod p ) ,p p 也有很大概率为质数。
将两边同时约去一个 a a ,则有 a p − 1 ≡ 1 ( mod p ) a p − 1 ≡ 1 ( mod p ) 。
也即是说:假设我们要测试 n n 是否为质数。我们可以随机选取一个数 a a ,然后计算 a n − 1 ( mod n ) a n − 1 ( mod n ) ,如果结果不为 1 1 ,我们可以 100 % 100 % 断定 n n 不是质数。否则我们再随机选取一个新的数 a a 进行测试。如此反复多次,如果每次结果都是 1 1 ,我们就假定 n n 是质数。
二次探测定理
该测试被称为 F e r m a t F e r m a t 测试 。M i l l e r M i l l e r 和 R a b i n R a b i n 在 F e r m a t F e r m a t 测试上,建立了 M i l l e r − R a b i n M i l l e r − R a b i n 质数测试算法。与 F e r m a t F e r m a t 测试相比,增加了一个 二次探测定理 。
二次探测定理:如果 p p 是奇素数,则 x 2 ≡ 1 ( mod p ) x 2 ≡ 1 ( mod p ) 的解为 x ≡ 1 x ≡ 1 或 x ≡ p − 1 ( mod p ) x ≡ p − 1 ( mod p ) 。
这是很容易证明的:
x 2 ≡ 1 ( mod p ) x 2 − 1 ≡ 0 ( mod p ) ( x − 1 ) ( x + 1 ) ≡ 0 ( mod p ) (1) (2) (3) (1) x 2 ≡ 1 ( mod p ) (2) x 2 − 1 ≡ 0 ( mod p ) (3) ( x − 1 ) ( x + 1 ) ≡ 0 ( mod p )
又 ∵ ∵ p p 为奇素数,有且仅有 1 , p 1 , p 两个因子。
∴ ∴ 只有两解 x ≡ 1 x ≡ 1 或 x ≡ p − 1 ( mod p ) x ≡ p − 1 ( mod p ) 。
如果 a n − 1 ≡ 1 ( mod n ) a n − 1 ≡ 1 ( mod n ) 成立,M i l l e r − R a b i n M i l l e r − R a b i n 算法不是立即找另一个 a a 进行测试,而是看 n − 1 n − 1 是不是偶数。如果 n − 1 n − 1 是偶数,另 u = n − 1 2 u = n − 1 2 ,并检查是否满足二次探测定理即 a u ≡ 1 a u ≡ 1 或 a u ≡ n − 1 ( mod n ) a u ≡ n − 1 ( mod n ) 。
假设 n = 341 n = 341 ,我们选取的 a = 2 a = 2 。则第一次测试时,2 340 mod 341 = 1 2 340 mod 341 = 1 。由于 340 340 是偶数,因此我们检查 2 170 2 170 ,得到 2 170 mod 341 = 1 2 170 mod 341 = 1 ,满足二次探测定理。同时由于 170 170 还是偶数,因此我们进一步检查2 85 mod 341 = 32 2 85 mod 341 = 32 。此时不满足二次探测定理,因此可以判定 341 341 不为质数。
将这两条定理合起来,也就是最常见的 M i l l e r − R a b i n M i l l e r − R a b i n 测试 。
单次测试失败的几率为 1 4 1 4 。那么假设我们做了 t i m e s t i m e s 次测试,那么我们总错误的几率为 ( 1 4 ) t i m e s ( 1 4 ) t i m e s 如果 t i m e s t i m e s 为 20 20 次,那么错误概率约为 0.00000000000090949470 0.00000000000090949470 也就是意味着不可能发生的事件。
单次时间复杂度是 O ( log n ) O ( log n ) ,总复杂度就是 O ( t i m e s log n ) O ( t i m e s log n ) 了。注意 long long
会爆,用 __int128
就行了。
代码实现
问题描述
分解一个合数 n n ,时间效率要求较高。(比试除法 O ( √ n ) O ( n ) 要快许多)
算法解决
生日悖论
在一个班级里,假设有 60 60 人,所有人生日不同概率是多少?
依次按人考虑,第一个人有 1 1 的概率,第二个人要与第一个人不同就是 1 − 1 365 1 − 1 365 ,第三个人与前两人不同,那么就是 1 − 2 365 1 − 2 365 。那么第 i i 个人就是 1 − i − 1 365 1 − i − 1 365 。
那么概率就是
n ∏ i = 1 ( 1 − i − 1 365 ) = n − 1 ∏ i = 0 365 − i 365 = 365 n – – 365 n ∏ i = 1 n ( 1 − i − 1 365 ) = ∏ i = 0 n − 1 365 − i 365 = 365 n _ 365 n
假设我们带入 n n 等于 60 60 ,那么这个概率就是 0.0058 0.0058 ,也就是说基本上不可能发生。
好像是因为和大众的常识有些违背,所以称作生日悖论。
假设一年有 N N 天,那么只要 n ≥ √ N n ≥ N 存在相同生日的概率就已经大于 50 % 50 % 了。
利用伪随机数判断
本段参考了 Doggu 大佬的博客 。
我们考虑利用之前那个性质来判断,生成了许多随机数,然后两两枚举判断。
假设枚举的两个数为 i , j i , j ,假设 i > j i > j ,那么判断一下 gcd ( i − j , n ) gcd ( i − j , n ) 是多少,如果非 1 1 ,那么我们就已经找出了一个 n n 的因子了,这样的概率随着枚举的组数变多会越来越快。
如果我们一开始把这些用来判断的数都造出来这样,这样就很坑了。不仅内存有点恶心,而且其实效率也不够高。
考虑优化,我们用一个伪随机数去判断,不断生成两个随机数,然后像流水线一样逐个判断就行了。
有一个随机函数似乎很优秀,大部分人都用的这个:
f ( x ) = ( x 2 + a ) mod n f ( x ) = ( x 2 + a ) mod n
a a 在单次判断中是一个常数。
然后一般这种简单的随机数都会出现循环节,我们判断一下循环节就行了。
但为了节省内存,需要接下来这种算法。
Floyd 判圈法
两个人同时在一个环形跑道上起跑,一个人是另外一个人的两倍速度,那么这个人绝对会追上另外一个人。追上时,意味着其中一个人已经跑了一圈了。
然后就可以用这个算法把退出条件变松,只要有环,我们就直接退出就行了。
如果这次失败了,那么继续找另外一个 a a 就行了。
用 Miller-Rabin 优化
这个算法对于因子多,因子值小的数 n n 是较为优异的。
也就是对于它的一个小因子 p p , P o l l a r d − R h o P o l l a r d − R h o 期望在 O ( √ p ) O ( p ) 时间内找出来。
但对于 n n 是一个质数的情况,就没有什么较好的方法快速判断了,那么复杂度就是 O ( √ n ) O ( n ) 的。
此时就退化成暴力试除法了。
此时我们考虑用前面学习的 M i l l e r − R a b i n M i l l e r − R a b i n 算法来优化这个过程就行了。
此时把这两个算法结合在一起,就能解决这道题啦qwq
代码实现
我似乎要特判因子为 2 2 的数,那个似乎一直在里面死循环qwq
问题描述
首先 n n 个候选人围成一个圈,依次编号为 0 … n − 1 0 … n − 1 。然后随机抽选一个数 k k ,并 0 0 号候选人开始按从 1 1 到 k k 的顺序依次报数,n − 1 n − 1 号候选人报数之后,又再次从 0 0 开始。当有人报到 k k 时,这个人被淘汰,从圈里出去。下一个人从 1 1 开始重新报数。
现在询问:最后一个被淘汰在哪个位置。 ( n ≤ 10 9 , k ≤ 1000 ) ( n ≤ 10 9 , k ≤ 1000 ) 。
算法解决
暴力模拟
最直观的解法是用循环链表模拟报数、淘汰的过程,复杂度是 O ( n k ) O ( n k ) 。
递推一
令 f [ n ] f [ n ] 表示当有 n n 个候选人时,最后当选者的编号。 (注意是有 n n 个人,而不是 n n 轮)
{ f [ 1 ] = 0 f [ n ] = ( f [ n − 1 ] + k ) ( mod n ) n > 1 { f [ 1 ] = 0 f [ n ] = ( f [ n − 1 ] + k ) ( mod n ) n > 1
我们考虑用数学归纳法证明:
f [ 1 ] = 0 f [ 1 ] = 0
显然当只有 1 1 个候选人时,该候选人就是当选者,并且他的编号为 0 0 。
f [ n ] = ( f [ n − 1 ] + k ) ( mod n ) f [ n ] = ( f [ n − 1 ] + k ) ( mod n )
假设我们已经求解出了 f [ n − 1 ] f [ n − 1 ] ,并且保证 f [ n − 1 ] f [ n − 1 ] 的值是正确的。
现在先将 n n 个人按照编号进行排序:
那么第一次被淘汰的人编号一定是 k − 1 k − 1 (假设 k < n k < n ,若 k > n k > n 则为 ( k − 1 ) mod n ( k − 1 ) mod n )。将被选中的人标记为 # – – # _ :
第二轮报数时,起点为 k k 这个候选人。并且只剩下 n − 1 n − 1 个选手。假如此时把 k k 看作 0 ′ 0 ′ ,k + 1 k + 1 看作 1 ′ 1 ′ ... 则对应有:
此时在 0 ′ , 1 ′ , . . . , n − 2 ′ 0 ′ , 1 ′ , . . . , n − 2 ′ 上再进行一次 k k 报数的选择。而 f [ n − 1 ] f [ n − 1 ] 的值已经求得,因此我们可以直接求得当选者的编号 s ′ s ′ 。
但是,该编号 s ′ s ′ 是在 n − 1 n − 1 个候选人报数时的编号,并不等于 n n 个人时的编号,所以我们还需要将s ′ s ′ 转换为对应的 s s 。通过观察,s s 和 s ′ s ′ 编号相对偏移了 k k ,又因为是在环中,因此得到 s = ( s ′ + k ) mod n s = ( s ′ + k ) mod n ,即 f [ n ] = ( f [ n − 1 ] + k ) ( mod n ) f [ n ] = ( f [ n − 1 ] + k ) ( mod n ) 。
然后就证明完毕了。
这个时间复杂度就是 O ( n ) O ( n ) 的了,算比较优秀了,但对于这题来说还是不行。
递推二
因此当 n n 小于 k k 时,就只能采用第一种递推的算法来计算了。
那么我们就可以用第二种递推,解决的思路仍然和上面相同,而区别在于我们每次减少的 n n 的规模不再是 1 1 。
同样用一个例子来说明,初始 n = 10 , k = 4 n = 10 , k = 4 :
初始序列:
当 7 7 号进行过报数之后:
而对于任意一个 n , k n , k 来说,退出的候选人数量为 ⌊ n k ⌋ ⌊ n k ⌋ 。
由于此时起点为 8 8 ,则等价于:
因此我们仍然可以从 f [ 8 ] f [ 8 ] 的结果来推导出 f [ 10 ] f [ 10 ] 的结果。
我们需要根据 f [ 8 ] f [ 8 ] 的值进行分类讨论。假设 f [ 8 ] = s f [ 8 ] = s ,则根据 s s 和 n mod k n mod k 的大小关系有两种情况:
⎧ ⎨ ⎩ s ′ = s − n mod k + n ( s < n mod k ) s ′ = s − n mod k + ⌊ ( s − n mod k ) k − 1 ⌋ ( s ≥ n mod k ) { s ′ = s − n mod k + n ( s < n mod k ) s ′ = s − n mod k + ⌊ ( s − n mod k ) k − 1 ⌋ ( s ≥ n mod k )
上面那种就是最后剩的的几个数,对于上面例子就是 s = { 0 , 1 } s = { 0 , 1 } ,s ′ = { 8 , 9 } s ′ = { 8 , 9 } 。
下面那种就是其余的几个数,对于上面例子就是 s = { 5 , 6 , 7 } s = { 5 , 6 , 7 } ,s ′ = { 4 , 5 , 6 } s ′ = { 4 , 5 , 6 } 。
这两种就是先定位好初始位置,然后再加上前面的空隙,得到新的位置。
由于我们不断的在减小 n n 的规模,最后一定会将 n n 减少到小于 k k ,此时 ⌊ n k ⌋ = 0 ⌊ n k ⌋ = 0 。
因此当 n n 小于 k k 时,就只能采用第一种递推的算法来计算了。
每次我们将 n → n − ⌊ n k ⌋ n → n − ⌊ n k ⌋ ,我们可以近似看做把 n n 乘上 1 − 1 k 1 − 1 k 。
按这样分析的话,复杂度就是 O ( − log k − 1 k n + k ) O ( − log k − 1 k n + k ) 的。这个对于 k k 很小的时候会特别快。
代码实现
问题描述
对于三个自然数 a , b , c a , b , c ,求解 a x + b y = c a x + b y = c 的 ( x , y ) ( x , y ) 的整数解。
算法解决
首先我们要判断是否存在解,对于这个这个存在整数解的充分条件是 gcd ( a , b ) | c gcd ( a , b ) | c 。
也就是说 c c 为 a , b a , b 最大公因数的一个倍数。
朴素欧几里得
对于求解 gcd ( a , b ) gcd ( a , b ) 我们需要用 朴素欧几里得定理 。
gcd ( a , b ) = gcd ( b , a mod b ) gcd ( a , b ) = gcd ( b , a mod b ) 。
这个是比较好证明的:
假设 a = k ∗ b + r a = k ∗ b + r ,有 r = a mod b r = a mod b 。不妨设 d d 为 a a 和 b b 的一个任意一个公约数,则有 a ≡ b ≡ 0 ( mod d ) a ≡ b ≡ 0 ( mod d ) 。
由于同余的性质 a − k b ≡ r ≡ 0 ( mod d ) a − k b ≡ r ≡ 0 ( mod d ) 因此 d d 是 b b 和 a mod b a mod b 的公约数。
然后 ∀ d | gcd ( a , b ) ∀ d | gcd ( a , b ) 都满足这个性质,所以这个定理成立啦。
所以我们就可以得到算 gcd gcd 的一个简单函数啦。
这个复杂度是 O ( log ) O ( log ) 的,十分迅速。
然后判定是否有解后,我们需要在这个基础上求一组解 ( x , y ) ( x , y ) ,由于 a , b , c a , b , c 都是 gcd ( a , b ) gcd ( a , b ) 的倍数。
对于 a , b a , b 有负数的情况,我们需要将他们其中一个负数加上另外一个数直到非负。(由于前面朴素欧几里得定理是不会影响的)两个负数,直接将整个式子反号,然后放到 c c 上就行了。
我们将它们都除以 gcd ( a , b ) gcd ( a , b ) ,不影响后面的计算。
也就是我们先求对于 a x + b y = 1 a x + b y = 1 且 a ⊥ b a ⊥ b (互质)求 ( x , y ) ( x , y ) 的解。
接下来我们利用前面的朴素欧几里得定律推一波式子。
a x + b y = gcd ( a , b ) = gcd ( b , a mod b ) ⇒ b x + ( a mod b ) y = b x + ( a − ⌊ a b ⌋ b ) y = a y + b ( x − ⌊ a b ⌋ y ) (4) (5) (6) (7) (8) (4) a x + b y = gcd ( a , b ) (5) = gcd ( b , a mod b ) (6) ⇒ b x + ( a mod b ) y (7) = b x + ( a − ⌊ a b ⌋ b ) y (8) = a y + b ( x − ⌊ a b ⌋ y )
不难发现此时 x x 变成了 y y , y y 变成了 x − ⌊ a b ⌋ y x − ⌊ a b ⌋ y ,利用这个性质,我们可以递归的去求解 ( x , y ) ( x , y ) 。
边界条件其实和前面朴素欧几里得是一样的 b = 0 b = 0 的时候,我们有 a = 1 , a x + b y = 1 a = 1 , a x + b y = 1 那么此时 x = 1 , y = 0 x = 1 , y = 0 。
这样做完的话我们用 O ( log ) O ( log ) 的时间就会得到一组 ( x , y ) ( x , y ) 的特殊解。
解系扩展
但常常我们要求对于 x o r y x o r y 的最小非负整数解,这个的话我们需要将单个 ( x , y ) ( x , y ) 扩展成一个解系。
如果学过不定方程的话,就可以轻易得到这个解系的,在此不过多赘述了。
d ∈ Z { x = x 0 + d b y = y 0 − d a d ∈ Z { x = x 0 + d b y = y 0 − d a
要记住,( x , y ) ( x , y ) 都需要乘上 c c 。
然后我们直接令 x x 为 ( x mod b + b ) mod b ( x mod b + b ) mod b 就行了。
代码实现
超简短版本:
递归调用的话, y = x ′ , x = y ′ y = x ′ , x = y ′ ,只需要修改 y y 就行了。(是不是很好背啊)
定义
我们定义 φ ( x ) φ ( x ) 为 小于 x x 的正整数中与 x x 互质的数的个数,称作欧拉函数。数学方式表达就是
φ ( x ) = ∑ i < x [ i ⊥ x ] φ ( x ) = ∑ i < x [ i ⊥ x ]
但需要注意,我们定义 φ ( 1 ) = 1 φ ( 1 ) = 1 。
性质
若 x x 为质数,φ ( x ) = x − 1 φ ( x ) = x − 1 。
证明:这个很显然了,因为除了质数本身的数都与它互质。
若 x = p k x = p k ( p p 为质数, x x 为单个质数的整数幂),则 φ ( x ) = ( p − 1 ) × p k − 1 φ ( x ) = ( p − 1 ) × p k − 1 。
证明:不难发现所有 p p 的倍数都与 x x 不互质,其他所有数都与它互质。
p p 的倍数刚好有 p k − 1 p k − 1 个(包括了 x x 本身)。
那么就有 φ ( x ) = p k − p k − 1 = ( p − 1 ) × p k − 1 φ ( x ) = p k − p k − 1 = ( p − 1 ) × p k − 1 。
若 p , q p , q 互质,则有 φ ( p × q ) = φ ( p ) × φ ( q ) φ ( p × q ) = φ ( p ) × φ ( q ) ,也就是欧拉函数是个积性函数。
证明 :
如果 a a 与 p p 互质 ( a < p ) ( a < p ) , b b 与 q q 互质 ( b < q ) ( b < q ) , c c 与 p q p q 互质 ( c < p q ) ( c < p q ) ,则 c c 与数对 ( a , b ) ( a , b ) 是一一对应关系。由于 a a 的值有 φ ( p ) φ ( p ) 种可能, b b 的值有 φ ( q ) φ ( q ) 种可能,则数对 ( a , b ) ( a , b ) 有 φ ( p ) φ ( q ) φ ( p ) φ ( q ) 种可能,而 c c 的值有 φ ( p q ) φ ( p q ) 种可能,所以 φ ( p q ) φ ( p q ) 就等于 φ ( p ) φ ( q ) φ ( p ) φ ( q ) 。
具体来说这一条需要 中国剩余定理 以及 完全剩余系 才能证明,感性理解一下它的思路吧。(后面再填)
对于一个正整数 x x 的质数幂分解 x = p 1 k 1 × p 2 k 2 × ⋯ × p n k n = n ∏ i = 1 p i k i x = p 1 k 1 × p 2 k 2 × ⋯ × p n k n = ∏ i = 1 n p i k i 。
φ ( x ) = x × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × ⋯ × ( 1 − 1 p n ) = x n ∏ i = 1 ( 1 − 1 p i ) φ ( x ) = x × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × ⋯ × ( 1 − 1 p n ) = x ∏ i = 1 n ( 1 − 1 p i )
证明:
我们考虑用前几条定理一起来证明。
φ ( x ) = n ∏ i = 1 φ ( p k i i ) = n ∏ i = 1 ( p i − 1 ) × p i k i − 1 = n ∏ i = 1 p i k i × ( 1 − 1 p i ) = x n ∏ i = 1 ( 1 − 1 p i ) (9) (10) (11) (12) (9) φ ( x ) = ∏ i = 1 n φ ( p i k i ) (10) = ∏ i = 1 n ( p i − 1 ) × p i k i − 1 (11) = ∏ i = 1 n p i k i × ( 1 − 1 p i ) (12) = x ∏ i = 1 n ( 1 − 1 p i )
若 p p 为 x x 的约数( p p 为质数, x x 为任意正整数),我们有 φ ( x × p ) = φ ( x ) × p φ ( x × p ) = φ ( x ) × p 。
证明:
我们利用之前的第 4 4 个性质来证明就行了。
不难发现 n ∏ i = 1 ( 1 − 1 p i ) ∏ i = 1 n ( 1 − 1 p i ) 是不会变的,前面的那个 x x 会变成 x × p x × p 。
所以乘 p p 就行了。
若 p p 不是 x x 的约数( p p 为质数, x x 为任意正整数),我们有 φ ( x × p ) = φ ( x ) × ( p − 1 ) φ ( x × p ) = φ ( x ) × ( p − 1 ) 。
证明 :p , x p , x 互质,由于 φ φ 积性直接得到。
求欧拉函数
求解单个欧拉函数
我们考虑质因数分解,然后直接利用之前的性质 4 4 来求解。
快速分解的话可以用前面讲的 P o l l a r d R h o P o l l a r d R h o 算法。
求解一系列欧拉函数
首先需要学习 线性筛 ,我们将其改一些地方。
质数 p p 的 φ ( p ) = p − 1 φ ( p ) = p − 1 。
对于枚举一个数 i i 和另外一个质数 p p 的积 x x 。
我们在线性筛,把合数筛掉会分两种情况。
p p 不是 i i 的一个约数,由于性质 5 5 就有 φ ( x ) = φ ( i ) × ( p − 1 ) φ ( x ) = φ ( i ) × ( p − 1 ) 。
p p 是 i i 的一个约数,此时我们会跳出循环,由于性质 6 6 有 φ ( x ) = φ ( i ) × p φ ( x ) = φ ( i ) × p 。
代码实现
扩展欧拉定理
a b ≡ ⎧ ⎪ ⎨ ⎪ ⎩ a b mod φ ( p ) a ⊥ p a b a ⊥/ p , b < φ ( p ) a b mod φ ( p ) + φ ( p ) a ⊥/ p , b ≥ φ ( p ) ( mod p ) a b ≡ { a b mod φ ( p ) a ⊥ p a b a ⊥̸ p , b < φ ( p ) a b mod φ ( p ) + φ ( p ) a ⊥̸ p , b ≥ φ ( p ) ( mod p )
证明看 这里 ,有点难证,记结论算啦qwq
这个常常可以用于降幂这种操作。它的一个扩展就是最前面的那个 费马小定理 。
问题描述
给定了 n n 组除数 m i m i 和余数 r i r i ,通过这 n n 组 ( m i , r i ) ( m i , r i ) 求解一个 x x ,使得 x mod m i = r i x mod m i = r i 这就是 模线性方程组 。
数学形式表达就是 :
⎧ ⎪
⎪
⎪
⎪ ⎨ ⎪
⎪
⎪
⎪ ⎩ x ≡ r 1 ( mod m 1 ) x ≡ r 2 ( mod m 2 ) ⋮ x ≡ r n ( mod m n ) { x ≡ r 1 ( mod m 1 ) x ≡ r 2 ( mod m 2 ) ⋮ x ≡ r n ( mod m n )
求解一个 x x 满足上列所有方程。
算法解决
中国剩余定理
中国剩余定理提供了一个较为通用的解决方法。(似乎是唯一一个以中国来命名的定理)
如果 m 1 , m 2 , … , m n m 1 , m 2 , … , m n 两两互质,则对于任意的整数 r 1 , r 2 , … , r n r 1 , r 2 , … , r n 方程组 ( S ) ( S ) 有解,并且可以用如下方法来构造:
令 M = n ∏ i = 1 m i M = ∏ i = 1 n m i ,并设 M i = M m i M i = M m i ,令 t i t i 为 M i M i 在模 m i m i 意义下的逆元(也就是 t i × M i ≡ 1 ( mod m i ) t i × M i ≡ 1 ( mod m i ) )。
不难发现这个 t i t i 是一定存在的,因为由于前面要满足两两互质,那么 M i ⊥ m i M i ⊥ m i ,所以必有逆元。
那么在模 M M 意义下,方程 ( S ) ( S ) 有且仅有一个解 : x = ( n ∑ i = 1 r i t i M i ) mod M x = ( ∑ i = 1 n r i t i M i ) mod M 。
不难发现这个特解是一定满足每一个方程的,只有一个解的证明可以考虑特解 x x 对于第 i i 个方程,下个继续满足条件的 x x 为 x + m i x + m i 。那么对于整体来说,下个满足条件的数就是 x + l c m ( m 1 , m 2 , … , m n ) = x + M x + l c m ( m 1 , m 2 , … , m n ) = x + M 。
严谨数学证明请见 百度百科 。
利用扩欧合并方程组
我们考虑合并方程组,比如从 n = 2 n = 2 开始递推。
{ x ≡ r 1 ( mod m 1 ) x ≡ r 2 ( mod m 2 ) { x ≡ r 1 ( mod m 1 ) x ≡ r 2 ( mod m 2 )
也就等价于
{ x = m 1 × k 1 + r 1 x = m 2 × k 2 + r 2 { x = m 1 × k 1 + r 1 x = m 2 × k 2 + r 2
此处 k 1 , k 2 ∈ N k 1 , k 2 ∈ N 。联立后就得到了如下一个方程:
m 1 × k 1 + r 1 = m 2 × k 2 + r 2 m 1 × k 1 − m 2 × k 2 = r 2 − r 1 (13) (14) (13) m 1 × k 1 + r 1 = m 2 × k 2 + r 2 (14) m 1 × k 1 − m 2 × k 2 = r 2 − r 1
我们令 a = m 1 , b = m 2 , c = r 2 − r 1 a = m 1 , b = m 2 , c = r 2 − r 1 就变成了 a x + b y = c a x + b y = c 的形式,用之前讲过的 扩展欧几里得 ,可以求解。
首先先判断有无解。假设存在解,我们先解出一组 ( k 1 , k 2 ) ( k 1 , k 2 ) ,然后带入解出 x = x 0 x = x 0 的一个特解。
我们将这个可以扩展成一个解系:
x = x 0 + k × l c m ( m 1 , m 2 ) , k ∈ N x = x 0 + k × l c m ( m 1 , m 2 ) , k ∈ N
由于前面不定方程的结论, k 1 k 1 与其相邻解的间距为 m 2 gcd ( m 1 , m 2 ) m 2 gcd ( m 1 , m 2 ) ,又有 x = m 1 × k 1 + r 1 x = m 1 × k 1 + r 1 。所以 x x 与其相邻解的距离为 m 1 m 2 gcd ( m 1 , m 2 ) = l c m ( m 1 , m 2 ) m 1 m 2 gcd ( m 1 , m 2 ) = l c m ( m 1 , m 2 ) 。
所以我们令 M = l c m ( m 1 , m 2 ) , R = x 0 M = l c m ( m 1 , m 2 ) , R = x 0 则又有新的模方程 x ≡ R ( mod M ) x ≡ R ( mod M ) 。
然后我们就将两个方程合并成一个了,只要不断重复这个过程就能做完了。
这个比 中国剩余定理 优秀的地方就在于它这个不需要要求 m m 两两互质,并且可以较容易地判断无解的情况。
代码实现
注意有些细节,比如求两个数的 g c d g c d 的时候,一定要先除再乘,防止溢出!!
问题描述
求
( n m ) mod p ( n m ) mod p
1 ≤ m ≤ n ≤ 10 18 , 2 ≤ p ≤ 10 6 1 ≤ m ≤ n ≤ 10 18 , 2 ≤ p ≤ 10 6 不保证 p p 为质数。
问题求解
虽说是扩展卢卡斯,但是和卢卡斯定理没有半点关系。
用了几个性质,首先可以考虑 p p 分解质因数。
假设分解后
p = ∏ i p i k i p = ∏ i p i k i
我们可以对于每个 p i p i 单独考虑,假设我们求出了 ( n m ) mod p i k i ( n m ) mod p i k i 的值。
然后我们可以考虑用前文提到的 C R T C R T 来进行合并(需要用扩欧求逆元)。
问题就转化成如何求 ( n m ) mod p i k i ( n m ) mod p i k i 了。
首先我们考虑它有多少个关于 p i p i 的因子(也就是多少次方)。
我们令 f ( n ) f ( n ) 为 n ! n ! 中包含 p i p i 的因子数量,那么 ( n m ) = n ! m ! ( n − m ) ! ( n m ) = n ! m ! ( n − m ) ! ,所以因子数量就为 f ( n ) − f ( m ) − f ( n − m ) f ( n ) − f ( m ) − f ( n − m ) 。
那如何求 f ( n ) f ( n ) 呢。
我们举出一个很简单的例子来讨论。
n = 19 , p i = 3 , k i = 2 n = 19 , p i = 3 , k i = 2 时:
n ! = 1 ∗ 2 ∗ 3 ∗ ⋯ ∗ 19 = ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ∗ 10 ∗ 11 ∗ 13 ∗ 14 ∗ 16 ∗ 17 ∗ 19 ) ∗ 3 6 ∗ 6 ! ≡ ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ∗ 19 ∗ 3 6 ∗ 6 ! ≡ ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ∗ 19 ∗ 3 6 ∗ 6 ! ( mod 3 2 ) (15) (16) (17) (18) (15) n ! = 1 ∗ 2 ∗ 3 ∗ ⋯ ∗ 19 (16) = ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ∗ 10 ∗ 11 ∗ 13 ∗ 14 ∗ 16 ∗ 17 ∗ 19 ) ∗ 3 6 ∗ 6 ! (17) ≡ ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ∗ 19 ∗ 3 6 ∗ 6 ! (18) ≡ ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ∗ 19 ∗ 3 6 ∗ 6 ! ( mod 3 2 )
不难发现每次把 p i p i 倍数提出来的东西,就是 ⌊ n p i ⌋ ! ⌊ n p i ⌋ ! ,那么很容易得到如下递推式:
f ( n ) = f ( ⌊ n p i ⌋ ) + ⌊ n p i ⌋ f ( n ) = f ( ⌊ n p i ⌋ ) + ⌊ n p i ⌋
不难发现这个求单个的复杂度是 O ( log n ) O ( log n ) 的,十分优秀。
然后考虑剩下与 p i p i 互不相关的如何求,不难发现在 p i k i p i k i 意义下会存在循环节(比如前面的 ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ( 1 ∗ 2 ∗ 4 ∗ 5 ∗ 7 ∗ 8 ) 2 ,可能最后多出来一个或者多个不存在循环节中的数。
不难发现循环节长度是 < p i k i < p i k i ,因为同余的在 > p i k i > p i k i 之后马上出现,然后把多余的 ⌊ n p i ⌋ ⌊ n p i ⌋ 的部分递归处理就行了。
然后就可以快速处理出与 p i p i 无关的了,最后合并一下就行了。
简介算法流程:
对于每个 p i k i p i k i 单独考虑。
用 f ( n ) f ( n ) 处理出有关于 p i p i 的因子个数。
然后递归处理出无关 p i p i 的组合数的答案。
把这两个乘起来合并即可。
最后用 C R T C R T 合并所有解即可。
瞎分析的复杂度( Great_Influence 博客上抄的)。(不可靠)
O ( ∑ p i k i log n ( log p i n − k ) + p i log p ) ∼ O ( p log p ) O ( ∑ p i k i log n ( log p i n − k ) + p i log p ) ∼ O ( p log p )
代码解决
#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
using namespace std;
typedef long long ll;
template <typename T> inline bool chkmin (T &a, T b) { return b < a ? a = b, 1 : 0 ; }
template <typename T> inline bool chkmax (T &a, T b) { return b > a ? a = b, 1 : 0 ; }
void File () {
#ifdef zjp_shadow
freopen ("P4720.in" , "r" , stdin);
freopen ("P4720.out" , "w" , stdout);
#endif
}
ll n, m, p;
void Exgcd (ll a, ll b, ll &x, ll &y) {
if (!b) x = 1 , y = 0 ;
else Exgcd (b, a % b, y, x), y -= a / b * x;
}
inline ll fpm (ll x, ll power, ll Mod) {
ll res = 1 ;
for (; power; power >>= 1 , (x *= x) %= Mod)
if (power & 1 ) (res *= x) %= Mod;
return res;
}
inline ll fac (ll n, ll pi, ll pk) {
if (!n) return 1 ;
ll res = 1 ;
For (i, 2 , pk) if (i % pi) (res *= i) %= pk;
res = fpm (res, n / pk, pk);
For (i, 2 , n % pk) if (i % pi) (res *= i) %= pk;
return res * fac (n / pi, pi, pk) % pk;
}
inline ll Inv (ll n, ll Mod) {
ll x, y; Exgcd (n, Mod, x, y);
return (x % Mod + Mod) % Mod;
}
inline ll CRT (ll b, ll Mod) {
return b * Inv (p / Mod, Mod) % p * (p / Mod) % p;
}
inline ll factor (ll x, ll Mod) {
return x ? factor (x / Mod, Mod) + (x / Mod) : 0 ;
}
inline ll Comb (ll n, ll m, ll pi, ll pk) {
ll k = factor (n, pi) - factor (m, pi) - factor (n - m, pi);
if (!fpm (pi, k, pk)) return 0 ;
return fac (n, pi, pk) * Inv (fac (m, pi, pk), pk) % pk * Inv (fac (n - m, pi, pk), pk) % pk * fpm (pi, k, pk) % pk;
}
inline ll ExLucas (ll n, ll m) {
ll res = 0 , tmp = p;
For (i, 2 , sqrt (p + .5 )) if (!(tmp % i)) {
ll pk = 1 ; while (!(tmp % i)) pk *= i, tmp /= i;
(res += CRT (Comb (n, m, i, pk), pk)) %= p;
}
if (tmp > 1 ) (res += CRT (Comb (n, m, tmp, tmp), tmp)) %= p;
return res;
}
int main () {
File ();
cin >> n >> m >> p;
printf ("%lld\n" , ExLucas (n, m));
return 0 ;
}
__EOF__
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!