判断质数

单个素数判断

暴力

质数是因数仅有 \(1\) 和自身的正整数,所以仅需判断从 \(2\)\(n-1\) 是否是他的因子,若都不是那么该数就是质数,容易写出暴力代码:

bool isp(int x){
	for(int i=2;i<x;++i)if(!(x%i))return 0;
	return 1;
}

复杂度: \(O(n)\)

优化

这样的写法过于暴力,对于仅一个数 \(x\) 的判质需 \(O(n)\),是不能接受的,因此可以从质数的本身的性质出发进行优化:

\(x\) 非质数,那么对于 \(x\) 的正整数因子集合 \(A\) 中,若 \(\forall n(1<n<x)\in A\),有 \({\frac{x}{n}}\) \(\in A\).特殊地,当 \(x=n^2时,{\frac{x}{n}}={n}\).
也就是说只要知道 \(n\)\(x\) 的因子,就知道 \({\frac{x}{n}}\)\(x\) 的因子,对于因子的遍历仅需从 \(2\)\(\sqrt n\).

bool isp(int x){                            
	for(int i=2;i*i<=x;++i)if(!(x%i))return 0;  
	return 1;  
}

复杂度: \(O(\sqrt n)\)

进一步优化

质数规律(引自这里 ):

  1. 当数大于 \(\color{red}10\) 之后,质数的结尾均以 \(\color{green}1,3,7,9\) 结尾.
  2. 规律二:从 \(\color{red}5\) 开始,质数均分布在 \(\color{green}6\) 的倍数附近,比如 \(\color{green}12,18,24,30\) 等附近均有质数.

设待判数 \(x=6k+b(x>=6)\),有

\[\begin{cases} x\mod 2=0 &for&\text{b=0,2,4}\\ x\mod 3=0 &for&\text{b=3} \end{cases}\]

那么判质的过程变为

  1. \(x<=6\) 时直接特判.
  2. \(b\neq 1,5\) 时绝对非质数.
  3. \(b=1,5\) 时,仅枚举质数 \(i=6k+1\)\(6k+5\) 是否为其因子,来加快寻找速度:
bool isp(int x){                                          
     if(x==2||x==3)return 1;          
     if(x==1||x%6!=1&&x%6!=5)return 0;//特判x<=6以及b!=1,5
     
     int tmp=sqrt(x);            
     for(int i=5;i<=tmp/*或(i*i<=x)*/;i+=6)if(x%i==0||x%(i+2)==0)return 0;          
     return 1;                                                                    
}

复杂度: \(O(\frac{\sqrt n}3)\) (视作 \(O(\sqrt n)\))

其他方法

  • ( 123 ): \(O(\log^3 n)\)\(O((\log n)^5)\)
    (前者不确定,后者确定)

线性筛质数

当判断质数的次数足够大的时候,我们自然希望采用记忆化的思想\(x\) 是否为质数提前预知,使得每次询问的复杂度降至 \(\color{red}O(1)\).

怎么做呢?容易想到若提前知道要询问的所有数中最大的 \(x\),将 \(1\)\(n\) 的数中所有质数筛出,如果还是采用常规思路,对 \(1\sim n\) 均进行一次单判,复杂度为 \(\color{red}O(n^2)\),代价惊人.

此时应采取反向思维,用因子去筛合数.用变量 \(i\) 遍历 \(2\sim n\) \(\color{red}(在后面我们将遍历到数字i称作第i层)\),则当 \(x=ki\color{green}(i,k\in N且i,k>1)\) 时为合数并将其打上标记,一次遍历下来,没有标记的就是质数.

但对于每个 \(i\),不经过剪枝情况下 \(k\in(2,+\infty)\) ,怎么选择 \(k\) 的取值决定复杂度.显然一个合数必然有质因子,那么仅用他的最小质因子 \(k\) 筛掉他,否则时停止在第 \(i\) 层的筛数,这样每个数仅仅被筛掉一次,复杂度 \(O(n)\).

欧拉筛将第 \(i\) 层之前筛的质数放进集合 \(A\) 中,集合 \(A\) 显然单调递增.指针\(j\)从小到大遍历令 \(k=A_j\) ,筛掉 \((k*i)\),当 \(k\)\(i\) 的因子时,可见在第 \(k\) 层时已经筛过 \(i\) 了,那么 \(i\) 便不用往下筛了.

因为 \(k\times i=k^2\times (\large\frac{i}{k})\) ,也就是在第 \(k^2\) 层时会用 \(\frac{i}{k}\) 筛掉 \((k*i)\) 这个数,以此保证每个数有且仅有一次被筛到,复杂度 \(\color{red}O(n)\).

vector<int>pri;                        
vector<bool>npri;            
int n;  
void euler(){  
	npri=vector<bool>(n+1),npri[1]=1;  
	  for(int i=2;i<=n;++i){  
		if(!npri[i])pri.emplace_back(i);  
		  for(int k:pri){  
			if(i*k>n)break;  
			npri[i*k]=1;  
			if(i%k==0)break;//i乘上其他质数的结果会被 k 的倍数筛掉,所以直接break
		}
	}
}
posted @ 2025-08-20 20:54  badn  阅读(81)  评论(0)    收藏  举报