质数
:除了1和他本身没有其它因数的正整数就是质数。1不是质数,2是质数。
x
的质数,令y从2到
x
\sqrt[]{x}
x?(向下取整,比如2.4=2)依次尝试 ,如果x%y=0,那么x不是质数。(2要单独讨论,否则按照这个逻辑2不是质数)。
答
:如果取到
x
\sqrt[]{x}
x?还没有找到x的约数,那么就可以断定x一定是质数!!。
x
\sqrt[]{x}
x?*
x
\sqrt[]{x}
x?=x,这是一个特殊的位置。可以将这个等式抽象为a * b=x。a和b的关系是你消我涨,即a=
x
b
{x \over b}
bx?。
x
\sqrt[]{x}
x?是a=b的特殊情况,如果a小于等于
x
\sqrt[]{x}
x?的数里没有x的约数,那么当a大于
x
\sqrt[]{x}
x?的时候一样没有,因为a小于等于
x
\sqrt[]{x}
x?的时候,b是大于等于
x
\sqrt[]{x}
x?的。排除a不是x的约数的同时将b也排除了,因为约数是成对出现的。所以当a大于
x
\sqrt[]{x}
x?的时候实际上在做重复的计算,相当于把a,b交换了一个位置,实际上当a取值小于等于
x
\sqrt[]{x}
x?的时候已经把所有可能的约数排除了。答
:第一个问题解释了,约数是成对出现的,只需要枚举a从2到
x
\sqrt[]{x}
x?即可,那么b=
x
a
{x \over a}
ax?同时被考虑到了。如果
x
\sqrt[]{x}
x?不是整数,那么就向下取整,去掉小数部分,这样可以保证a的枚举没有遗漏(a小于等于
x
\sqrt[]{x}
x?)。如果向上取整,那么a的取值大于
x
\sqrt[]{x}
x?,b小于
x
\sqrt[]{x}
x?。这个组合实际上之前可能已经被排除了,所以重复计算了一对约数。可能会遗漏约数导致判断错误。答
:代码的逻辑是:从2到
x
\sqrt[]{x}
x?依次找x的约数,但是2本身可以被2整除,这样就会误判2有约数,所以2要特判一下。#include<iostream>
#include<cmath>
using namespace std;
bool violence(int n) //violence 暴力
{
if (n == 1)return false;
if (n == 2)return true;
for (int i = 2; i <= sqrt(n); i++) //sqrt函数向下取整的
{
if (n % i == 0)return false; //可以被i整除,不是质数
}
return true;
}
int main()
{
for (int i = 1; i <= 100; i++) // 找到1-100内的质数
{
if (violence(i))cout << i << " ";
}
return 0;
}
答
:当遍历到 i 的时候,2~i-1的所有倍数都被标记成合数了,如果此时i没有被标记,说明i一定是质数。因为i的约数一定是在[2,i-1]的区间内的,而这个区间的所有数的倍数都不能构成i,说明这些数都不是i的约数,那么i就是质数。int primes[N], cnt; // primes[]存储所有质数,cnt记录质数的下标
bool st[N]; // st[i]==true表示i不是质数
void get_prime(int n) //挑选2到n中所有的质数
{
for(int i = 2; i <= n; i ++)
{
if(!st[i]) //当前的数是i,如果st[i]==false,那么说明i这个数一定是质数(理解不了先不解释,看后面的代码)
primes[cnt ++] = i; //存储当前的质数
for(int j = i + i; j <= n; j += i) //将i的所有倍数都标记为true,因为i的倍数一定不是质数。
st[j] = true;
}
}
数的公理( 两种形式)
(
1
)
任意一个正整数(大于等于
2
)
=
x
1
k
1
?
x
2
k
2
?
.
.
.
.
?
x
n
k
n
(1)任意一个正整数(大于等于2)=x_1^{k_1}*x_2^{k_2}*....*x_n^{k_n}
(1)任意一个正整数(大于等于2)=x1k1???x2k2???....?xnkn??
(
2
)
任意一个正整数(大于等于
2
)
=
x
1
k
1
?
(
x
2
k
2
?
.
.
.
.
?
x
n
k
n
)
(2)任意一个正整数(大于等于2)=x_1^{k_1}*(x_2^{k_2}*....*x_n^{k_n})
(2)任意一个正整数(大于等于2)=x1k1???(x2k2???....?xnkn??)
其中
x
n
x_n
xn?都是质因数,
k
n
k_n
kn?是大于等于0的正整数
优化原理
普通优化是将 i 之前的所有数的倍数都标记,以此判断i是不是合数。但是数的公理第2个形式说明了,一个数必然是由若干个质数的乘积构成的,那么就不需要取将i之前所有数的倍数标记,只需要将i之前的所有质数的乘积标记就可以了。
int primes[N], cnt; // primes[]存储所有质数,cnt记录质数的下标
bool st[N]; // st[i]==true表示i不是质数
void get_primes(int n)//挑选2到n中所有的质数
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) //如果st[i]==true,说明i不是质数
continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i) //只要把质数的倍数筛选掉就可以了。
st[j] = true;
}
}
前集回顾
埃式筛选法有一个很明显的缺陷,即筛选(标记)合数的时候有大量重复的步骤。比如:
当 i = 3的时候,将6,9,,,51,,都标记了。当 i = 17 的时候,会标记,34,51,,,发现有重复的项51。
目的和公理
原理
任何数只有一个最小质因数,只要保证每个合数都是最小质因数筛选的,就可以保证不会重复筛选。(有点难以理解)
int primes[N], cnt; // primes[]存储所有质数,cnt记录质数的下标
bool st[N]; // st[i]==true表示i不是质数
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i; //合理性证明最底下
for (int j = 0; primes[j] <= n/i; j ++ )
{
st[primes[j] * i] = true; //筛选的合数primes[j] * i的最小质因数一定是primes[j],就用primes[j]去筛选它
if (i % primes[j] == 0) //为了避免重复筛选合数。
//如果i % primes[j] == 0,则primes[j]是i的最小质因数,同时说明primes[j]是i*primes[j]的最小质因数(因为primes[j]是递增的),
//如果继续循环,下一步就是st[primes[j+1]*i]=true,因为primes[j+1]>primes[j],primes[j+1]<=i,所以primes[j+1]*i的最小质因数仍然是primes[j],
//但是根据原理,一个数需要被他的最小质因数筛选才不会重复,这里却用primes[j+1]筛选了primes[j+1]*i。重复筛选了。
break;
}
}
}
i | 筛掉的数 |
---|---|
2 | 4 |
3 | 6,9 |
4 | 8 |
5 | 10,15,25 |
6 | 12 |
7 | 14,21,35,49 |
8 | 16 |
9 | 19,27 |
10 | 20 |
11 | 22,33,55,77,121 |
Question区:
1.为什么st[i]=false可以表示是质数(明明下面的for循环和埃式筛选法不一样)
答
:i=
x
1
x_1
x1?*
x
2
x_2
x2?,
x
1
x_1
x1?表示x的最小质因数,
x
1
x_1
x1?是[2,i-1]里面的一个质数,
x
2
x_2
x2?可以是质数也可以是合数。(1)
x
2
x_2
x2?是质数:我们从代码可以归纳出,当k(2~i-1)是质数的时候,它会筛掉所有的k * primes[j](j从0到cnt-1), 所以i会被[2,i-1]内的某一个质数筛掉。(2)
x
2
x_2
x2?是合数:我们从代码归纳出,当k是合数的时候,会筛选出[2,k的最小质因子] * primes[j], 而
x
1
x_1
x1?是最小质因数,那么一定有一个k会筛选出
x
2
x_2
x2?.
2.为什么要if (i % primes[j] == 0)break;
答
:按照原理来的,每个数都要由他的最小质因数来更新,如果说这一步不退出而是继续循环,那么下一步i * primes[j+1]将被筛选掉,但是i * primes[j+1]的最小质因数是primes[j](因为i不变,上一步i%primes[j]==0且primes[j+1]>primes[j]),而现在i * primes[j+1]由primes[j+1]来筛选了,这样导致筛选过程提前了,后面又会筛选导致重复(当i变大的时候会再次筛选一次)。
线性筛选法还是很难理解的。本质在于:每个数只被筛选一次,且是被去最小质因数筛选的。