素数与筛法相关

一.素数定义与判定.

素数:又称质数,一个大于 1 1 1的正整数 n n n定义为素数,当且仅当不存在一个数字 i ∈ [ 2 , n ) i\in [2,n) i[2,n)满足 i ∣ n i|n in.

之后我们将素数集记为 P P P.

合数:一个大于 1 1 1的正整数定义为合数,当且仅当它不是个素数.

根据素数的定义,我们可以很容易的写出一个判定素数的代码:

bool Check_prime(int n){
  if (n<=1) return 0;
  for (int i=2;i<n;++i)
    if (n%i==0) return 0;
  return 1;
}

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

显然每次都枚举 n n n个数来判定有些浪费,所以考虑优化这个算法.

容易注意到一个性质,若两个小于 n n n的数 x , y x,y x,y相乘要等于 n n n,则 x , y x,y x,y中必然有一个数不超过 n \sqrt{n} n ,所以我们只需要枚举到 n \sqrt{n} n 就可以了.

时间复杂度 O ( n ) O(\sqrt{n}) O(n ).

代码如下:

bool Check_prime(int n){
  if (n<=1) return 0;
  for (int i=2;i*i<=n;++i)
    if (n%i==0) return 0;
  return 1;
}



二.素数个数相关.

素数个数函数:定义素数个数 π ( n ) \pi(n) π(n)为:
π ( n ) = ∑ i = 1 n [ i ∈ P ] \pi(n)=\sum_{i=1}^{n}[i\in P] π(n)=i=1n[iP]

素数定理 π ( n ) ≈ n ln ⁡ n \pi(n)\approx \frac{n}{\ln n} π(n)lnnn.

素数定理的推论1:第 n n n个素数的大小约为 n ln ⁡ n n\ln n nlnn.

素数定理的推论2:设第 i i i个素数为 p i p_i pi,有:
O ( ∑ p i ≤ n 1 p i ) = O ( log ⁡ log ⁡ n ) . O\left(\sum_{p_i\leq n}\frac{1}{p_i}\right)=O(\log \log n). O(pinpi1)=O(loglogn).

证明:
O ( ∑ p i ≤ n 1 p i ) = O ( ∑ i ≤ π ( n ) 1 i log ⁡ i ) = O ( ∫ 1 π ( n ) 1 x log ⁡ x d x ) = O ( log ⁡ log ⁡ π ( n ) ) = O ( log ⁡ log ⁡ n ) O\left(\sum_{p_i\leq n}\frac{1}{p_i}\right)=O\left(\sum_{i\leq \pi(n)}\frac{1}{i\log i}\right)\\ =O\left(\int_{1}^{\pi(n)}\frac{1}{x\log x}\mathrm{d} x\right)\\ =O(\log \log \pi(n))\\ =O(\log \log n) O(pinpi1)=Oiπ(n)ilogi1=O(1π(n)xlogx1dx)=O(loglogπ(n))=O(loglogn)

证毕.


三.素数筛法的引入与埃式筛法.

考虑如何求出 [ 1 , n ] [1,n] [1,n]中的所有素数.

根据上面的判定,我们可以很容易写出一个 O ( n n ) O(n\sqrt{n}) O(nn )的算法:

bool Check_prime(int n){
  if (n<=1) return 0;
  for (int i=2;i*i<=n;++i)
    if (n%i==0) return 0;
  return 1;
}

int n,pr[N+9],cp; 

void Sieve(){
  for (int i=2;i<=n;++i)
    if (Check_prime(i)) pr[++cp]=i;
}

但是这个复杂度好像并不能满足我们的需求,有没有更快的算法呢?

考虑不要一个一个枚举后判定,而是开一个标记数组 b b b.对于每个数 i i i,我们把 2 i , 3 i , 4 i , ⋯ 2i,3i,4i,\cdots 2i,3i,4i,都标记为 0 0 0,最后把所有标记为 1 1 1的数取出就是素数.

容易发现,所有非素数 i i i的倍数肯定会被 i i i的某个因数筛掉,所以我们就不用枚举合数的倍数了,直接枚举所有素数的倍数筛就好了.

时间复杂度 O ( n log ⁡ log ⁡ n ) O(n\log \log n) O(nloglogn).

代码如下:

int n,b[N+9],pr[N+9],cp;

void Sieve(){
  for (int i=2;i<=n;++i){
  	if (!b[i]) pr[++cp]=i;
  	for (int j=1;j<=cp&&i*pr[j]<=n;++j) b[i*pr[j]]=1;
  }
}

以上筛法被称为埃式筛法.

埃式筛法还可以在 O ( ( r + r − l ) log ⁡ log ⁡ r ) O((\sqrt{r}+r-l)\log \log r) O((r +rl)loglogr)的复杂度内筛出区间 [ l , r ] [l,r] [l,r]内的素数,具体参见POJ2689 Prime Distance.


四.线性筛法.

事实上,还有一种比埃式筛更快的线性筛法,学名欧拉筛,它的时间复杂度是稳定 O ( n ) O(n) O(n)的.

它的具体思路是,在埃式筛法的基础上,让每一个数都只被它的最小素因子筛掉.

具体来说,我们枚举 j j j并筛掉 i p j ip_j ipj的时候,若 p j ∣ i p_j|i pji,那么 p j + 1 p_{j+1} pj+1必然不是 i p j + 1 ip_{j+1} ipj+1的最小素因子,此时就可以直接退出了.

代码如下:

int n,b[N+9],pr[N+9],cp;

void Sieve(){
  for (int i=2;i<=n;++i){
  	if (!b[i]) pr[++cp]=i;
  	for (int j=1;j<=cp&&i*pr[j]<=n;++j){
  	  b[i*pr[j]]=1;
  	  if (i%pr[j]==0) break;
  	} 
  }
}



五.素因子与唯一分解定理.

素因子:定义一个数 a a a n n n的素因子,当且仅当 a ∈ P a\in P aP a ∣ n a|n an.

唯一分解定理:对于任意一个正整数 n n n,必然可以被唯一分解为:
n = ∏ i = 1 k p i c i n=\prod_{i=1}^{k}p_i^{c_i} n=i=1kpici

其中 p i ∈ P p_i\in P piP.

结论1:一个数 n n n的素因子个数不超过 log ⁡ n \log n logn个,即:
∑ i = 1 k c i ≤ log ⁡ n \sum_{i=1}^{k}c_i\leq \log n i=1kcilogn

结论2:一个数 n n n本质不同的素因子个数不超过 log ⁡ log ⁡ n \log \log n loglogn个,即:
k ≤ log ⁡ log ⁡ n k\leq \log \log n kloglogn

现在我们又有了一个新问题,如何分解出 n n n的所有 p i p_i pi与对应的 c i c_i ci.

首先一个显然的性质,必然只有最多一个素因子大于 n \sqrt{n} n ,所以我们可以只从 2 2 2枚举到 n \sqrt{n} n .

此时我们存一个暂存变量 n o w now now初始为 n n n,每当我们枚举到一个数 i ∣ n o w i|now inow,就让 n o w now now不停除 i i i n o w now now没有 i i i这个因数,此时把 i i i放入 p p p中并把除的此时作为对应的 c c c即可.

时间复杂度 O ( n ) O(\sqrt{n}) O(n ).

代码如下:

int d[2][C+9],cd;

void Get_d(int n){
  int now=n;
  for (int i=2;i*i<=n;++i)
    if (now%i==0)
      for (d[0][++cd]=i;now%i==0;now/=i) ++d[1][cd];
  if (now>1) d[0][++cd]=now,d[1][cd]=1;
}



六.求1到n的唯一分解.

现在我们有一个新的问题,如何求 [ 1 , n ] [1,n] [1,n]中所有数的唯一分解.

我们当然可以用上面单个数的分解方法做到 O ( n n ) O(n\sqrt{n}) O(nn )分解,但是还可以用筛法做到 O ( n log ⁡ n ) O(n\log n) O(nlogn).

具体来说是这样的,由于在线性筛的时候每次都是用最小的素因子筛掉某个数,所以我们可以顺便记录每个数的最小素因子.有了每个数的最小素因子之后,我们就可以用一个递归或者循环来求解了.

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下:

int n,b[N+9],pr[N+9],cp;
int ml[N+9];

void Sieve(){
  for (int i=2;i<=n;++i){
  	if (!b[i]) pr[++cp]=i,ml[i]=i;
  	for (int j=1;j<=cp&&i*pr[j]<=n;++j){
  	  b[i*pr[j]]=1;
  	  ml[i*pr[j]]=pr[j];
  	  if (i%pr[j]==0) break;
  	}
  }
}

int d[2][N+9][C+9],cd[N+9];

void Get_d(){ 
  for (int i=2;i<=n;++i){
  	int now=i;
  	for (;now>1;now/=ml[now])
  	  if (ml[now]==d[0][i][cd[i]]) ++d[1][i][cd[i]];
  	  else d[0][i][++cd[i]]=ml[now],d[1][i][cd[i]]=1;
  }
}

//主程序中
Sieve();
Get_d();



七.阶乘的素因子个数统计.

我们有方法在 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度内统计 n ! n! n!的某个素因子 p p p的出现次数.

首先,我们先把 [ 1 , n ] [1,n] [1,n]中有因数 p p p的数的个数,显然为 ⌊ n p ⌋ \left\lfloor\frac{n}{p}\right\rfloor pn.

然后,我们发现所有 p p p的都有了 1 1 1的贡献,但是 p 2 p^2 p2的倍数会有 2 2 2的贡献,所以 p 2 p^2 p2的倍数的贡献应该再加上 1 1 1,也就是 ⌊ n p 2 ⌋ \left\lfloor\frac{n}{p^2}\right\rfloor p2n.

以此类推,我们可以得到答案为:
a n s = ∑ i = 1 + ∞ ⌊ n p i ⌋ ans=\sum_{i=1}^{+\infty}\left\lfloor\frac{n}{p^{i}}\right\rfloor ans=i=1+pin

显然计算这个式子可以做到 O ( log ⁡ n ) O(\log n) O(logn).


八.Miller-Rabbin素性测试.

如何用快于 O ( n ) O(\sqrt{n}) O(n )的时间复杂度确定 n n n是否是一个素数呢?

一个不幸的事实,保证正确性的做法中,最快的是 O ( n ) O(\sqrt{n}) O(n )的朴素和一个被称为AKS素性测试的上界至少为 O ( log ⁡ 6 n ) O(\log^{6}n) O(log6n)的算法.

但是我们为什么一定要让这个算法一定正确?有没有什么虽然不能确保正确但正确率很高的做法呢?

考虑费马小定理的逆命题,即:
a n − 1 ≡ 1    ( m o d    n ) ⇒ a ∈ P a^{n-1}\equiv 1\,\,(mod\,\,n)\Rightarrow a\in P an11(modn)aP

这个命题虽然不是真命题,但它的出错概率非常小,通常随机个位数个 a a a就可以保证很高的正确率.

同时有一个二次探测定理:对于一个素数 n n n,若有 x x x满足 x 2 ≡ 1    ( m o d    n ) x^2\equiv 1\,\,(mod\,\,n) x21(modn),则必然有 x = 1 x=1 x=1 x = n − 1 x=n-1 x=n1.

证明:
首先 n = 2 n=2 n=2时定理显然成立.
x 2 ≡ 1    ( m o d    n ) ⇔ n ∣ ( x 2 − 1 ) ⇔ n ∣ ( x + 1 ) ( x − 1 ) ⇔ n ∣ ( x + 1 ) ∨ n ∣ ( x − 1 ) x^2\equiv 1\,\,(mod\,\,n)\Leftrightarrow n|(x^2-1)\Leftrightarrow n|(x+1)(x-1)\Leftrightarrow n|(x+1)\vee n|(x-1) x21(modn)n(x21)n(x+1)(x1)n(x+1)n(x1)

由于 n ∈ P n\in P nP,所以只能取 x = 1 x=1 x=1 x = n − 1 x=n-1 x=n1.
证毕.

这个时候我们考虑在判定一个数 n n n是否为素数的时候,先把 2 2 2 n n n为偶数的情况判掉.之后把 n − 1 n-1 n1中所有 2 2 2提出来后的值记为 t t t,并把 2 2 2的个数记为 c n t cnt cnt,即 n − 1 = 2 c n t t n-1=2^{cnt}t n1=2cntt.

然后对于每一个随机值 a a a,若 a t ≡ 1    ( m o d    n ) a^{t}\equiv 1\,\,(mod\,\,n) at1(modn) a t ≡ n − 1    ( m o d    n ) a^{t}\equiv n-1\,\,(mod\,\,n) atn1(modn),则判定这是个素数.

否则,我们把 c n t cnt cnt 2 2 2乘回去,记当前值为 n o w now now,若 n o w 2 ≡ 1    ( m o d    n ) now^2\equiv 1\,\,(mod\,\,n) now21(modn) n o w ≢ 1    ( m o d    n ) now\not\equiv1\,\,(mod\,\,n) now1(modn) n o w ≢ n − 1    ( m o d    n ) now\not\equiv n-1\,\,(mod\,\,n) nown1(modn),说明不满足二次探测定理,判定这不是个素数.否则把 n o w now now平方继续下一次操作.

注意通常要这个算法来处理的时候, n 2 n^2 n2的大小已经可以爆long long了,所以需要用到快速乘.

时间复杂度 O ( k log ⁡ n ) O(k\log n) O(klogn),其中 k k k表示取 a a a的次数.

代码如下:

const int pr[9]={2,3,5,7,11,13,17,19,23};

LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
LL add(LL a,LL b,LL p){return a+b>=p?a+b-p:a+b;}
LL sub(LL a,LL b,LL p){return a-b<0?a-b+p:a-b;}

LL mul(LL a,LL b,LL p){
  LL t=(long double)a/p*b+1e-8,res=a*b-t*p;
  return (res<0?res+p:res)%p;
}

void sadd(LL &a,LL b,LL p){a=add(a,b,p);}
void ssub(LL &a,LL b,LL p){a=sub(a,b,p);}
void smul(LL &a,LL b,LL p){a=mul(a,b,p);}
LL Power(LL a,LL k,LL p){LL res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}

bool Miller_rabbin(LL n){
  if (n==2) return 1;
  if (n&1^1||n<=1) return 0;
  LL t=n-1;
  int cnt=0;
  for (;t&1^1;t>>=1) ++cnt;
  for (int i=0;i<9;++i){
  	if (n==(LL)pr[i]) return 1;
  	LL a=Power(pr[i]%n,t,n);
	if (a==1||a==n-1) continue;
	for (int j=1;j<=cnt;++j){
	  LL t=mul(a,a,n);
	  if (a^1&&a^n-1&&t==1) return 0;
	  a=t;
	}
	if (a^1) return 0;
  }
  return 1;
}



九.Pollard-rho算法.

Pollard-rho算法是一种用于寻找一个数 n n n的不是 1 1 1 n n n的因数的算法.

Pollard-rho算法的基本思想是,生成一个随机数列 f i f_i fi,然后把随机数列中相邻两项做差并取绝对值,用这些值去和 n n n取gcd,只要取出的gcd大于 1 1 1且小于 n n n就可以分解出这个因数.

有一个较为优秀的伪随机函数 f i = ( f i − 1 2 + c )    m o d    n f_i=(f_{i-1}^2+c)\,\,mod\,\,n fi=(fi12+c)modn,其中 c c c是一个随机的常数,通过这个伪随机函数有较大概率得到一个优秀的因数.

同时,由于数列是伪随机数列,所以还是会不可避免出现循环.此时我们有一个Floyd判环法,从起点出发,记录两个值 f i , f 2 i f_i,f_{2i} fi,f2i,一个用一倍速跑,一个用两倍速跑,若它们相同说明出现循环,随即跳出重新选择一个随机的 c c c.

不过具体实现的时候,我们可以不用真的记录两个值跑,而是在跑了 2 i 2^{i} 2i的距离后记录一下当前值 t t t,直到跑到 2 i + 1 2^{i+1} 2i+1前都与 t t t比较是否出现循环.

同时通过Pollard-rho算法配合分治得到一个数 n n n的所有素因子的时间复杂度是期望 O ( n 1 4 log ⁡ n ) O(n^{\frac{1}{4}}\log n) O(n41logn)的,不过是在随机数列是真随机数列的时候.

若随机数列为 f i = ( f i − 1 2 + c )    m o d    n f_i=(f_{i-1}^2+c)\,\,mod\,\,n fi=(fi12+c)modn,则这样实现的Pollard-rho算法复杂度还是未知的,不过一般情况下我们仍然将其看为 O ( n 1 4 log ⁡ n ) O(n^{\frac{1}{4}}\log n) O(n41logn).

代码如下:

const int pr[9]={2,3,5,7,11,13,17,19,23};

LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
LL add(LL a,LL b,LL p){return a+b>=p?a+b-p:a+b;}
LL sub(LL a,LL b,LL p){return a-b<0?a-b+p:a-b;}

LL mul(LL a,LL b,LL p){
  LL t=(long double)a/p*b+1e-8,res=a*b-t*p;
  return (res<0?res+p:res)%p;
}

void sadd(LL &a,LL b,LL p){a=add(a,b,p);}
void ssub(LL &a,LL b,LL p){a=sub(a,b,p);}
void smul(LL &a,LL b,LL p){a=mul(a,b,p);}
LL Power(LL a,LL k,LL p){LL res=1;for (;k;k>>=1,smul(a,a,p)) if (k&1) smul(res,a,p);return res;}

bool Miller_rabbin(LL n){
  if (n==2) return 1;
  if (n&1^1||n<=1) return 0;
  LL t=n-1;
  int cnt=0;
  for (;t&1^1;t>>=1) ++cnt;
  for (int i=0;i<9;++i){
  	if (n==(LL)pr[i]) return 1;
  	LL a=Power(pr[i]%n,t,n);
	if (a==1||a==n-1) continue;
	for (int j=1;j<=cnt;++j){
	  LL t=mul(a,a,n);
	  if (a^1&&a^n-1&&t==1) return 0;
	  a=t;
	}
	if (a^1) return 0;
  }
  return 1;
}

LL Pollard_rho(LL n){
  if (Miller_rabbin(n)) return n;
  for (;2333;){
  	LL x=rand()%(n-1)+1,y=x;
  	int c=rand()%(n-1)+1,len=2;
  	for (int i=0;2333;){
  	  x=add(mul(x,x,n),c,n);
  	  LL g=gcd(abs(x-y),n);
	  if (g>1&&g<n) return g;
	  if (x==y) break;
  	  if (++i==len) y=x,len<<=1;
  	}
  }
}

set<LL>s;

void Divide_s(LL n){
  if (n==1||Miller_rabbin(n)) {if (n^1) s.insert(n);return;}
  LL t=Pollard_rho(n);
  for (;t==n;t=Pollard_rho(n)); 
  for (;n%t==0;n/=t);
  Divide_s(t);
  Divide_s(n);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值