• 数论基础2


    文章太长,前一部分点这里

    排列组合

    这部分内容也许不属于数论,但经常会和数论放到一起考。

    定义

    排列: A n m A_n^m Anm 为从 n n n 个物品中拿 m m m 个物品出来排列的方案数。有些旧教材也会写成 P n m P_n^m Pnm
        A n m = P n m = n ! ( n − m ) ! A_n^m=P_n^m=\dfrac{n!}{(n-m)!} Anm=Pnm=(nm)!n!
    组合: C n m C_n^m Cnm 为从 n n n 个物品中拿 m m m 个物品出来组合的方案数。也可以写成 ( n m ) \dbinom nm (mn)
        C n m = ( n m ) = A n m m ! = n ! m ! ( n − m ) ! C_n^m=\dbinom nm=\dfrac{A_n^m}{m!}=\dfrac{n!}{m!(n-m)!} Cnm=(mn)=m!Anm=m!(nm)!n!

    性质

    1. C n 0 = C n n = 1 1.\quad C_n^0=C_n^n=1 1.Cn0=Cnn=1
    2. C n k = C n n − k 2.\quad C_n^k=C_n^{n-k} 2.Cnk=Cnnk
      证明: C n n − k = n ! ( n − k ) ! ( n − ( n − k ) ) ! = n ! k ! ( n − k ) ! = C n k Cnkn=n!(nk)!(n(nk))!=n!k!(nk)!=Ckn Cnnk=(nk)!(n(nk))!n!=k!(nk)!n!=Cnk
    3. C n + 1 k + 1 = C n k + C n k + 1 3.\quad C_{n+1}^{k+1}=C_n^k+C_n^{k+1} 3.Cn+1k+1=Cnk+Cnk+1
      证明: k k k 个不同的数中取 n n n 个,第 k k k 个数如果取的话有 C n − 1 k − 1 C_{n−1}^{k−1} Cn1k1 种取法,如果不取则有 C n k − 1 C_n^{k−1} Cnk1 种。
    4. C m + r + 1 r = ∑ i = 0 r C m + i i 4.\quad C_{m+r+1}^r=\sum\limits_{i=0}^r C_{m+i}^i 4.Cm+r+1r=i=0rCm+ii
      证明:根据组合数递推公式,有:
          ∑ i = 0 r C m + i i = C m 0 + C m + 1 1 + C m + 2 2 + ⋯ + C m + r r = C m 1 + C m + 1 1 + C m + 2 2 + . . . + C m + r r = C m + 2 1 + C m + 2 2 + . . . + C m + r r … = C m + r + 1 r ri=0Cim+i=C0m+C1m+1+C2m+2++Crm+r=C1m+C1m+1+C2m+2+...+Crm+r=C1m+2+C2m+2+...+Crm+r=Crm+r+1 i=0rCm+ii=Cm0+Cm+11+Cm+22++Cm+rr=Cm1+Cm+11+Cm+22+...+Cm+rr=Cm+21+Cm+22+...+Cm+rr=Cm+r+1r
    5. C m n × C n r = C m r × C m − r n − r 5. \quad C_m^n\times C_n^r=C_m^r\times C_{m-r}^{n-r} 5.Cmn×Cnr=Cmr×Cmrnr
      证明:代入定义式即可。
          C m n × C n r = m ! n ! ( m − n ) ! × n ! r ! ( n − r ) ! = m ! r ! ( m − r ) ! × ( m − r ) ! ( m − n ) ! ( n − r ) ! = C m r × C m − r n − r Cnm×Crn=m!n!(mn)!×n!r!(nr)!=m!r!(mr)!×(mr)!(mn)!(nr)!=Crm×Cnrmr Cmn×Cnr=n!(mn)!m!×r!(nr)!n!=r!(mr)!m!×(mn)!(nr)!(mr)!=Cmr×Cmrnr
    6. ( a + b ) n = ∑ r = 0 n C n r a n n − r b r 6. \quad (a+b)^n=\sum\limits_{r=0}^nC_n^ra_n^{n-r}b^r 6.(a+b)n=r=0nCnrannrbr
      证明:百度百科
    7. n × C m n = m × C m − 1 n − 1 7.\quad n\times C_m^n=m\times C_{m-1}^{n-1} 7.n×Cmn=m×Cm1n1
      证明:代入定义式即可。
          n × C m n = n × m ! n ! ( m − n ) ! = m ! ( n − 1 ) ! ( m − n ) ! = m × ( m − 1 ) ! ( n − 1 ) ! ( m − n ) ! = m × C m − 1 n − 1 n×Cnm=n×m!n!(mn)!=m!(n1)!(mn)!=m×(m1)!(n1)!(mn)!=m×Cn1m1 n×Cmn=n×n!(mn)!m!=(n1)!(mn)!m!=m×(n1)!(mn)!(m1)!=m×Cm1n1

    组合数取模

    公式

    可不可以直接套用公式呢?答案是肯定的。

    逆元

    很抱歉,由于公式中有除法运算,因此需要求逆元,而模 p p p 意义下的逆元并不一定存在,因此这种方法仅仅使用于 p p p 为质数的情况下。
    根据通项公式 C n m = n ! m ! ( n − m ) ! C_n^m=\dfrac{n!}{m!(n-m)!} Cnm=m!(nm)!n!,可以发现,只需要预处理出阶乘的逆元和阶乘即可。

    #define C(n,m) (((fac[n]*fac_inv[m])%mod*fac_inv[(n)-(m)])%mod)
    
    • 1
    卢卡斯定理

    结论: C n m ≡ C ⌊ n p ⌋ ⌊ m p ⌋ × C n  mod  p m  mod  p ( m o d p ) CnmCpnpm×Cn mod pm mod p(modp)
    证明:设 a = ⌊ n p ⌋ , b = n  mod  p , c = ⌊ m p ⌋ , d = m  mod  p a=\Big\lfloor\dfrac{n}{p}\Big\rfloor,b=n\ \text{mod}\ p,c=\Big\lfloor\dfrac mp\Big\rfloor,d=m\ \text{mod}\ p a=pn,b=n mod p,c=pm,d=m mod p
       对于质数 p p p费马小定理得  a p − 1 ≡ 1   ( m o d p ) a^{p-1}≡1\ \pmod p ap11 (modp)
       两边同时乘以 a a a,得  a p ≡ a   ( m o d p ) a^p≡a\ \pmod p apa (modp)
       因此  ( 1 + x ) p ≡ 1 + x ≡ 1 + x p ( m o d p ) (1+x)^p\equiv 1+x\equiv1+x^p\pmod p (1+x)p1+x1+xp(modp)
       有了这个性质就很开心了。为了书写方便,接下来所有同余运算皆在模 p p p 意义下进行。
        ( 1 + x ) n ≡ ( 1 + x ) a ⋅ p ( 1 + x ) b ≡ ( 1 + x p ) a ( 1 + x ) b ≡ ∑ i = 0 k C a i x p i ∑ j = 0 k C b j x j (1+x)n(1+x)ap(1+x)b(1+xp)a(1+x)bi=0kCaixpij=0kCbjxj
       观察等式两边的 x m x^m xm 的系数,可得:
       等式左边 x m x^m xm 的系数,可以发现:
        C n m ≡ C a i C b j ( p i + j = m , j < p ) ≡ C a c C b d ≡ C ⌊ n p ⌋ ⌊ m p ⌋ C n  mod  p m  mod  p CnmCaiCbj(pi+j=m,j<p)CacCbdCpnpmCn mod pm mod p

    组合数奇偶

    如果让你计算 C n m  mod  2 C_n^m\ \text{mod}\ 2 Cnm mod 2 你会?

    1. 计算时间复杂度 O ( n ) O(n) O(n)
    2. 卢卡斯定理时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)
      如果我告诉你可以做到 O ( 1 ) O(1) O(1),你会有什么反应?
      结论:设有 x x x 满足 C n m ≡ x ( m o d 2 ) C_n^m≡x\pmod 2 Cnmx(mod2),则有
      x = { 0 ( x a n d m = m ) 1 ( x a n d m ≠ m ) x=
      {0(xandm=m)1(xandmm)" role="presentation" style="position: relative;">{0(xandm=m)1(xandmm)
      x={01(x(xandandm=m)m=m)

    证明:(坑)

    质数

    定义:若 x x x 只含有 1 1 1 x x x 两个因数,则 x x x 为质数,也叫做素数。

    判断质数

    暴力

    滚滚滚,谁用你讲这个。这不是重点,重点是后面的。
    那就直接上代码啦!

    bool prime(int x){
    	if(x==1) return false;
    	const int l=x-1;
    	for(int i=2;i<=l;i++)
    		if(!(x%i))
    			return false;
    	return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    时间复杂度 O ( n ) O(n) O(n)

    初步优化

    可以发现,一个数的因数总是成对出现(包括自身和自身)
    而其中的一个因数总是小于 x \sqrt x x 因此,枚举时只需要枚举到 x \sqrt x x 就好了。

    bool prime(int x){
    	if(x==1) return false;
    	const int l=floor(sqrt(x)+0.5);
    	for(int i=2;i<=l;i++)
    		if(!(x%i))
    			return false;
    	return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    时间复杂度 O ( n ) O(\sqrt n) O(n )

    进一步优化

    什么,还可以优化?我怎么不知道?
    你当然不知道。因为这里用到了一个很神仙的结论。
    结论:大于等于 5 5 5 的质数一定和 6 6 6 的倍数相邻。
    证明:令 x ⩾ 1 x\geqslant1 x1 ,将大于等于 5 5 5 的自然数表示如下:
    6 x − 1 , 6 x , 6 x + 1 , 6 x + 2 , 6 x + 3 , 6 x + 4 , 6 x + 5 , … 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,\dots 6x1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,
    其中, 6 x − 1 6x-1 6x1 6 x + 1 6x+1 6x+1 6 x + 5 6x+5 6x+5 6 x 6x 6x 相邻.
    6 x + 2 = 2 ( 3 x + 1 ) 6x+2=2(3x+1) 6x+2=2(3x+1),合数。
    6 x + 3 = 3 ( 2 x + 1 ) 6x+3=3(2x+1) 6x+3=3(2x+1),合数。
    6 x + 4 = 2 ( 3 x + 2 ) 6x+4=2(3x+2) 6x+4=2(3x+2),合数。
    因此,质数必然形如 6 x − 1 6x-1 6x1 6 x + 1 6x+1 6x+1
    这样就可以很好地降低时间复杂度了。

    bool primes(int x){
        if(x==1) return false;
        if(x==2||x==3)
    		return true;
    	if(x%6!=1&&x%6!=5)
    		return false;
        const int l=floor(sqrt(x)+0.5);
        for(int i=2;i<=l;i++)
            if(!(x%i))
                return false;
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    时间复杂度 O ( 1 ) ∼ O ( n ) O(1)\sim O(\sqrt n) O(1)O(n )

    再优化

    什么!还可以再优化!很肯定地告诉你是的。
    是不是感觉自己的智商被侮辱了?
    对于一个数 x x x,如果 x x x 可以整除一个合数,那么 x x x 一定可以整除 y y y 的任何一个质因数。
    这样我们就只需要判断它是否能整除小于 x \sqrt x x 的质数就可以了。
    可我们又怎么得到质数?虽然我们不能直接得到质数,但是我们可以得到一些很有可能是质数的数。
    根据上一小节的结论,可以发现质数必然靠近 6 6 6 的倍数。因此判断的时候,循环步长可以为 6 6 6
    代码:

    bool prime( int x ){  
    	if(x==1) return false;
    	if(x==2||x==3)
    		return true;
    	if(x%6!=1&&x%6!=5)
    		return false;
    	int tmp=floor(sqrt(x)+0.5);
    	for(int i=5;i<=tmp;i+=6)
    		if(x%i==0||x%(i+2)==0)
    			return false;
    	return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    时间复杂度: O ( 1 ) ∼ O ( n 6 ) O(1)\sim O(\frac{\sqrt n}6) O(1)O(6n )

    终极法则

    为什么这一小节的标题中不含有“优化”二字了呢?因为我们又换算法了。

    费马算法

    回忆一下费马小定理。


    ( a , p ) = 1 , p ∈ p r i m e (a,p)=1,p\in prime (a,p)=1,pprime ,则 a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1\pmod p ap11(modp)


    那么反过来看,对于一个正整数 p p p,如果存在一个正整数 a a a,满足 a p − 1 ≢ 1 ( m o d p ) a^{p-1}\not≡1\pmod p ap11(modp),那么我们可以肯定 p p p 是合数,因为如果 p p p 是质数,则与费马小定理矛盾。

    二次探测定理

    费马定理是一个RP算法,即能在多项式时间内求出答案,但对于否定状态可以做肯定判断(即100%不是质数),但对于肯定状态却不一定能做出肯定判断(即不一定是质数),可是它的正确率实在太低了,因此需要更强的算法。
    结论:若 p ∈ p r i m e p\in prime pprime,存在 x x x 满足 x < p , x ∈ N ∗ , x 2 ≡ 1 ( m o d p ) xx<p,xN,x21(modp)
    \quad\qquad 则有 x ≡ 1 ( m o d p ) x≡1\pmod p x1(modp) x ≡ p − 1 ( m o d p ) x≡p-1\pmod p xp1(modp)
    证明: ∵ x 2 ≡ 1 ( m o d p ) \because x^2≡1\pmod p x21(modp)
      ∴ x 2 − 1 ≡ 0 ( m o d p ) \qquad\ \therefore x^2-1≡0\pmod p  x210(modp)
      ∴ ( x + 1 ) ( x − 1 ) ≡ 0 ( m o d p ) \qquad\ \therefore (x+1)(x-1)≡0\pmod p  (x+1)(x1)0(modp)
      ∴ x ≡ 1 ( m o d p )  或  x ≡ p − 1 ( m o d p ) \qquad\ \therefore x≡1\pmod p\ 或\ x≡p-1\pmod p  x1(modp)  xp1(modp)
    和费马定理合起来就是完整的Miller-Rabin算法了。
    举个例子:当 p = 341 , a = 2 p=341,a=2 p=341,a=2 时,可以发现, a p − 1 ≡ 2 340 ≡ 1 ( m o d p ) a^{p-1}≡2^{340}≡1\pmod p ap123401(modp),通过了费马测试。
    这种时候,Miller-Rabin算法并不是马上换一个 a a a,而是二次探测。发现 340 340 340 是个偶数,则令 u = 340 2 = 170 u=\frac{340}2=170 u=2340=170,然后检测 a u ≡ 2 170 ≡ 1 ( m o d 341 ) a^u≡2^{170}≡1\pmod {341} au21701(mod341),发现满足二次探测定理,然后发现 170 170 170 还是个偶数,因此计算 a 85 ≡ 2 85 ≡ 34 ( m o d 341 ) a^{85}≡2^{85}≡34\pmod {341} a8528534(mod341)不满足二次探测定理,因此 341 341 341 不是质数。
    我们的老祖宗告诉我们,若 p p p 通过一次Miller-Rabin算法,则 p p p 不是质数的概率降低到 1 4 \dfrac 14 41
    这个概率还是很让人担忧,但是如果执行多次呢?执行 S S S 次算法后, p p p 不是质数的概率降低到了 1 4 S \dfrac 1{4^S} 4S1。因此,我们可以不断地生成随机数来判断,只要判断几次就能把概率提高。
    时间复杂度 O ( S l o g 2 n ) O(Slog_2n) O(Slog2n)

    取数方法

    一般底数仍然是随机选取,但当待测数不太大时,选择测试底数就有一些技巧了。

    测试数组被测上限例外
    2 , 3 2,3 2,3 1373653 1373653 1373653
    31 , 73 31,73 31,73 9080191 9080191 9080191
    2 , 7 , 61 2,7,61 2,7,61 4759123141 4759123141 4759123141
    2 , 13 , 23 , 1662803 2,13,23,1662803 2,13,23,1662803 1122004669633 1122004669633 1122004669633
    2 , 3 , 5 , 7 , 11 , 13 , 17 2,3,5,7,11,13,17 2,3,5,7,11,13,17 341550071728320 341550071728320 341550071728320
    2 , 3 , 7 , 61 , 24251 2,3,7,61,24251 2,3,7,61,24251 1 0 16 10^{16} 1016 46856248255981 46856248255981 46856248255981
    时间复杂度为: O ( k log ⁡ 2 k ) O(k\log_2k) O(klog2k),其中k是测试组数。

    筛法

    这里只讨论筛质数,筛函数请移步到莫比乌斯反演
    如果我需要求出一定范围内的质数表,你会怎么做?
    如果每次循环,并且判断某个数是否为质数当然可行,但没有用到线性计算的优势——在你计算某个答案的时候,前面的所有答案已经出来了。

    Eratosthenes筛法

    考虑换一种方法,每次把2的倍数的数全部从质数表中删去,然后再把把3的倍数的数全部从质数表中删去,以此类推,最后会得到完整的指数表。这就是最简单的 Eratosthenes 筛法。

    void eratosthenes_sieve(int n){
    	memset(vis,0,sizeof(vis));
    	for(int i=2;i<=n;i++)
    		for(int j=i*2;j<=n;j+=i)
    			vis[j]=1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这份代码尽管还可以优化,但已经非常快了。显而易见地,外层循环为 i i i 时,内层循环的次数是 ⌊ n i ⌋ − 1 < n i \lfloor\dfrac ni\rfloor-1<\dfrac ni in1<in,因此程序的时间复杂度为 O ( ∑ i = 2 n n i ) = O ( n ln ⁡ n ) O(\sum\limits^{n}_{i=2}\frac ni) =O(n\ln n) O(i=2nin)=O(nlnn),为什么?
    这是欧拉在1734年得到的结果 S = 1 + 1 2 + 1 3 + ⋯ + 1 n = ln ⁡ n + γ S=1+\frac 12+\frac 13+\dots+\frac 1n=\ln n+γ S=1+21+31++n1=lnn+γ,他发现, γ = S − ln ⁡ n γ=S-\ln n γ=Slnn 是一个定值,约等于 0.5772156649 0.5772156649 0.5772156649,这个定值也被称为欧拉常数,可惜的是目前人类对这个常数的了解还很少,甚至连它是个有理数还是无理数都不知道。
    类似地,其实外层循环只需要到 n \sqrt n n 就够了。并且内层循环从 i 2 i^2 i2 开始就好了。
    为什么?因为能被 i × 2 i\times 2 i×2 筛掉的数在 2 2 2 那一次循环中就已经被筛掉了。

    void eratosthenes_sieve(int n){
    	memset(vis,0,sizeof(vis));
    	int l=floor(sqrt(n)+0.5);
    	for(int i=2;i<=l;i++)
    		if(!vis[i])
    			for(int j=i*i;j<=n;j+=i)
    				vis[j]=1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    时间复杂度再一次降低。

    欧拉筛法

    欧拉筛法的优点不仅仅在于它可以证明时间复杂度是线性的,还在于可以同时出许多积性函数表。
    先上代码

    void euler_sieve(int n){
    	memset(vis,false,sizeof(vis));//是否是质数 
    	memset(prime,0,sizeof(prime));//质数表 
    	for(int i=2;i<=n;i++) {
    		if(!vis[i])
    			prime[cnt++]=i;//找到素数
    		for(int j=0;j<cnt&&i*prime[j]<=n;j++) {
    			vis[i*prime[j]]=true;//筛掉合数 
    			if(i%prime[j]==0)
    				break;	//(*)
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的(*)是关键,为什么i%prime[j]==0就可以退出了呢?
    这里规定,每个合数必须被其最小的质因数的倍数筛掉。
    因为 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1] 已经被筛掉了。可又是为什么?
    因为 i % p r i m e [ j ] = 0 i\%prime[j]=0 i%prime[j]=0,因此存在正整数 k k k 满足 i = p r i m e [ j ] ∗ k i=prime[j]*k i=prime[j]k
    后面将要筛掉的 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1] 可表示成 p r i m e [ j ] ∗ k ∗ p r i m e [ j + 1 ] prime[j]*k*prime[j+1] prime[j]kprime[j+1]
    p r i m e [ j ] < p r i m e [ j + 1 ] prime[j]prime[j]<prime[j+1]
    p r i m e [ j + 1 ] prime[j+1] prime[j+1] 并不是 i ∗ p r i m e [ j + 1 ] i*prime[j+1] iprime[j+1] 的最小质因数,而它也必然将要被 p r i m e [ j ] prime[j] prime[j] 的倍数筛掉。因此每个数只会被筛掉一次,因此时间复杂度为完美的 O ( n ) O(n) O(n)

  • 相关阅读:
    【C++编程语言】之类和对象---对象初始化和清除
    Nginx的安装
    speexdsp库实现音频3A算法,speexdsp库编译,C/C++语言
    java基础篇(1)
    力扣 307. 区域和检索 - 数组可修改
    备战蓝桥杯,那你一定得打这场免费且有现金奖励的算法双周赛!
    力扣1726. 同积元组
    Grpc简介
    【CNN-GRU预测】基于卷积神经网络-门控循环单元的单维时间序列预测研究(Matlab代码实现)
    unity快捷键
  • 原文地址:https://blog.csdn.net/linjiayang2016/article/details/126591274