猫史档案馆


【C++OI基础教程】数论(一):公式鬼畜,解题愉悦——素数筛与函数

用户:爵士OIer爵士OIer查看:2 回复:6 评论:2 创建时间:2020-07-20T22:20:14


 

这里讲的只是小学奥数的亿点拓展而已,推导鬼畜,代码感人,学习愉悦。

 

 

 

素数筛问题

简单地说,素数筛问题就是让你求一个区间内的素数。

 

有如下几种类型:

1.求素数个数

2.求第k个素数

等等。

 

 

原始解法

 

我们有一种极为喵的解法,O(n^2)。

枚举每个数,从2开始枚举每个小于此数的数。如果这个数是外层枚举的那个数的约数,表示外层枚举的数是合数,打上标记。

#include<bits/stdc++.h>
using namespace std;
int i,j,k,n,a[1000005],prime[1000005],cnt,q;
int main(){
	cin>>n>>q;
	for(i=2;i<n;i++)
		for(j=2;j<i;j++)
			if(!(i%j))a[i]=1;
	for(i=2;i<n;i++)
		if(!a[i]){
            cnt++;prime[cnt]=i;
        }
    while(q--){cin>>k;cout<<prime[k]<<endl;}
	return 0;
}

这代码绝对妥妥的T了~

怎么办?问题出在哪里?

(评测机emotion_doge

当然不是。

 

考虑优化:对于任意一个正整数n,必然有小于sqrt(n)(根号n)的约数。

因此,只需枚举j,只需要到j*j<=i就行了。

#include<bits/stdc++.h>
using namespace std;
int i,j,k,n,a[1000005],prime[1000005],cnt,q;
int main(){
	cin>>n>>q;
	for(i=2;i<n;i++)
		for(j=2;j*j<=i;j++)
			if(!(i%j))a[i]=1;
	for(i=2;i<n;i++)
		if(!a[i]){
            cnt++;prime[cnt]=i;
        }
    while(q--){cin>>k;cout<<prime[k]<<endl;}
	return 0;
}

结果呢?还是TLE。(一盆冷水)

问题出在哪?

(评测机emotion_doge

 

因此,我们需要使用——筛法

 

 

 

Eratosthenes 筛

 

 

这个筛法也是很原始的。

从2 开始,将每个数的各个倍数,标记成合数。

范围内所有未被标记的数,就是所求的全部素数。

for (int i = 2; i <= n; i++)
    isPrime [i] = true;
for (int i = 2; i <= n; i++)
    for (int j = i+i; j <= n; j+=i)
        isPrime [j] = false ;

然而这个代码还需要很多优化!(两盆冷水)

 

考虑优化:任一合数都是某个质数的倍数

所以只需要用质数去筛即可。

for (int i = 2; i <= n; i++)
    isPrime [i] = true;
    for (int i = 2; i <= n; i++)
        if ( isPrime [i]) {
            for (int j = i+i; j <= n; j+=i)
        isPrime [j] = false ;
    }

 

容易知道复杂度是

center_image

虽然很难证明,但是

center_image

 

 

很显然,这个代码仍然需要很多优化。

 

事实上一个n 以内的数若是合数,必然有不大于sqrt(n)的约数. 且若发现一个质数p,也不应该从2 * p 开始筛起,应该从p * p 筛起,因为更小的合数已经被别的质数筛掉了。

for (int i = 2; i <= n; i++)
    isPrime [i] = true;
    for (int i = 2; i*i <= n; i++)
        if ( isPrime [i]) {
            for (int j = i*i; j <= n; j+=i)
                isPrime [j] = false ;
        }

 

 

这个代码已经很优秀了。但是不幸的是,还有更好的方法。

 

这就需要用到

Euler 筛(线性筛)

 

 

之前的筛法,一个合数会被筛去多次(被它的每个质因数筛一次)

如果我们能保证每个合数只被筛一次,就能得到时间复杂度为线性的筛法。

Euler 筛就是这样的筛法,它保证每个合数只被其最小质因数筛去。

for (int i = 2; i <= n; i++) {
    if ( isPrime [i]) prime . push_back (i);
        for (int j = 0; j < prime .size (); j++) {
            if (i * prime [j] > N) break ;
            isPrime [i * prime [j]] = false ;
            if (i % prime [j] == 0) break ;
        }
}

 

当然每次被最大的质因数筛掉也是可以的:

for(int i=2;i<=n;i++)
{
	if(!check[i])prime[cnt++]=i;//若当前数i没有被之前的所有数筛掉,表明i是素数,将i添加进素数表prime
	for(int j=0;j<cnt&&i*prime[j]<100000001;j++)//注意i*prime[j]不要超过n的上限(10^8)
	{
		check[i*prime[j]]=1;//将当前素数prime[j]的i倍标记为合数
		if(i%prime[j]==0)break;//关键步骤:保证每个合数只被筛一次
	}
}

 

恭喜你,这个算法可以算是比较优的了。

 

 

 

下面讲解积性函数

积性函数

 

积性函数指对于所有互质的整数a和b有性质f(ab)=f(a)f(b)的数论函数

center_image

 

对于任意N,设

center_image

其中pi为互不相同的素数。

 

易知

center_image

 

求积性函数f的关键在于快速求出

center_image

这个东西。

 

euler 筛过程中,在筛掉n 的时候我们也得到了n 的最小质因数p

我们希望知道p 在n 中的次数k,这样就能利用

center_image

求出f(n)。

center_image

 

 

 

 课后习题

center_image


回复

上一页1 页 / 共 1下一页
阳光的流熔怪Uyt5阳光的流熔怪Uyt5

center_image

点赞1


评论


Mellin_AmpMellin_Amp

爵士高产似那啥()

 

点赞0


评论


一个STUB用户_6923702一个STUB用户_6923702

畸形函数/doge

点赞0


评论


LBIAOLBIAO

while(1){
    /*hehe~~*/
}

点赞0


评论


LBIAOLBIAO

#include<bits/stdc++.h>
using namespace std;
int n,flag=1;
bool check(int n)//求素数
{
	for(int i=2;i<=sqrt(n);i++)
	{
		if(n%i==0)return false;
	}
	return true;
}
int main()
{
	cin>>n;
	for(int i=2;i<=n-2;i++)
	{
		if(check(i+2)&&check(i))//如果i和i+2都是素数就输出
		{
			cout<<i<<" "<<i+2<<"\n";
			flag=0;//flag归0
		}
	}
	if(flag)cout<<"empty";
	return 0;
}//这些求的是素数对,再往下求就得到了数值

点赞0


评论


闫莫潮闫莫潮

看不懂一点,我要把我学机器人的同学拉来迫害他2333333

点赞1


评论