线性筛素数(欧拉筛)+前缀和优化
关于素数的定义:在大于1的自然数中,除了1和它本身以外不再有其他因数。
判断一个数是否是素数:
int x; // 要求的数 for(int i=2;i<=sqrt(x);++i) { if(x%i==0) { cout << "这不是素数" << endl; break; } }
埃氏筛法(时间复杂度:$O(NloglogN)$):
int num_prime = 0; // 素数的数量 int prime[5005]; // 放素数 int check[5005]; for(int i=2;i<5000;++i) { if(check[i]==0) { prime[num_prime++]=i; } for(int j=2*i;j<5000;j+=i) { check[j]=1; } }
这种方法比遍历每一个数从2到对数本身来取余判断是否为零的效率要高,可是里面仍然有重复筛选的现象。如下图所示,$2*2+2=6$,$2*3=6$,当数据区间大起来之后效率也会变得很差。
线性筛素数(时间复杂度:$O(N)$):
int num_prime = 0; // 素数的数量 int prime[5005]; // 放素数 int check[5005]; for(int i=2;i<5000;++i) { if(check[i]==0) { prime[num_prime++]=i; check[i]=1; // 状态1表示check[i]是素数 } for(int j=0;j<num_prime && i*prime[j]<=5000;++j) { check[i*prime[j]]=-1; // 状态-1表示check[i*prime[j]]不是素数,保存状态是为了可以线性查询 if(i%prime[j]==0) // 表示此时prime[j]是i的最小素数因子 { break; } } }
首先我们要理解:任何一个合数都可以表示成一个质数和一个数的乘积,例如$8%2==0$,表明当i=8的时候与第一个素数进行判断就可以停止进行下一轮了,如果再进行$8*3=24$,但$24=(2*4)*3=2*(4*3)=2*12$,表明筛24的最佳时机应该是i=12的时候,因为这时候12与其最小素因子2就可以一次筛出来了,并且不会与之前的24发生重复筛选的浪费.因此我们就可以按照一个数的最小素因子筛选来保证不出现重复筛选的情况,从而大大地提高了效率。
因为每一行都是相应最小素因子可以组成的数,因此可以证明所有的合数都会被筛掉
前缀和优化可以用来加快求区间和:求[left,right] -> sum[right] - sum[left-1] (left-1是因为sum[left]包含left元素)