1.素数的概念:自然数中存在着这样的数,他们只能被自己和整除。这样的数我们称之为素数。
2.素数几乎是数论中的半壁江山,十分的重要。对于一些与素数有关的操作,需要十分的熟练,今天我们一下来写写素数的几个基本的代码。
3.素数的几种打表:
(1)至于很简单的那种n^2或者nsqrt(n)的代码我们就不写了,这种代码是初学者写的,我们还是来写点有意义的。首先是筛选法求素数。第一种方法是埃氏筛:基本思想就是,素数的倍数一定不是素数,我们从第一个素数2开始,对素数的倍数一种打上标记,这样就把合数全部可以打上标记。素数就被筛选出来了。
代码实现:
const int n = 1e5 + 10;
int prime[n];
int check[n + 1];
void getPrime()
{
int tot = 0;
for (int i = 2; i <= n; i++)
{
if(!check[i])
{
prime[tot++] = i;
}
for (int j = i + i; j <= n; j += i)//这里就是在找倍数
{
check[j] = 1;
}
}
}
这种代码虽然看上去也是两层循环,但是第二层循环是对数级别的,所以复杂度降低到nloglongn.
但是很快,我们就发现了一些冗余操作,因为有的数据被标记了两次,例如6,我们使用2来标记的时候,它会被标记一次,但是使用3的时候,他还是会被标记一次。为了解决这个无谓的操作,达到每个数都被标记一次。出现了一种欧拉筛法,这种算法的时间复杂度是线性的,所以也叫做线性筛。提到这个问题,我们需要明白一个合数的性质,一个合数如果可以被写成一个合数和一个质数的乘积,那么他一定可以被写成一个更加小的质数和一个更加大的合数的乘积。根据这个性质,我们的这种算法保证每个数据一定会被它的最小的质因子筛选出去。
代码实现如下:
const int n = 1e5 + 10;
int prime[n];
int check[n + 1];
void getPrime()
{
int tot = 0;
for (int i = 2; i <= n; i++)
{
if (!check[i])
{
prime[tot++] = i;
}
for (int j = 0; j < tot; j++)
{
if (i*prime[j] > n)
{
break;
}
check[i*prime[j]] = 1;
if (i%prime[j] == 0)
{
break;
}
}
}
}
4.说到欧拉筛,我们就不得不提欧拉函数了。欧拉函数是这样的一个函数,这个函数只有一个自变量n,函数的结果就是不大于n并且与n互质的数据的个数。利用线性筛,我们可以在线性的时间内求出欧拉函数值。但是在这之前我们需要知道一些欧拉函数的基本性质。首先我们先简单的说一下什么叫做积性函数,故名思意就是可以乘积的函数。其实也就是这个意思,我们这里先简单的说什么叫做积性函数:对于互质的a,如果函数f满足f(ab)=f(a)*f(b)那么这样的函数就是积性函数。欧拉函数就是积性函数。
这是欧拉函数的性质,也是很好证明的,我们再这里就不再专门的证明了。还有一个结论:
这个结论也是很显然的,因为n是指数,他的因子只有1和本身,所以与他互质并且不大于他的除了他自己其他的都是。
知道了这三个结论,我们对欧拉函数打表就很简单了。
const int n = 1e5 + 10;
int prime[n];
int check[n + 1];
int phi[n];
void getPrime()
{
int tot = 0;
for (int i = 2; i <= n; i++)
{
if (!check[i])
{
prime[tot++] = i;
phi[i] = i - 1;//这里i是质数,根据我们第三条性质可得
}
for (int j = 0; j < tot; j++)
{
if (i*prime[j] > n)
{
break;
}
check[i*prime[j]] = 1;
if (i%prime[j] == 0)
{
phi[i*prime[j]] = phi[i] * prime[j];//第一条性质
break;
}
else
{
phi[i*prime[j]] = phi[i] * phi[prime[j]];//第二条性质
/*
这里由于prime[j]是质数,根据我们的性质3
phi[prime[j]]=(prime[j]-1);
所以这里这样写也是可以的:
phi[i*prime[j]]=phi[j]*(prime[j]-1);
*/
}
}
}
}