专题·质数【including 质数筛法,质因数分解,欧拉函数

初见安~本章讲一点儿数学知识最近讲的东西好像越来越基础了……

这里是基础数论专题(1)

1.质数

质数——若一个正整数除了它自己和1没有别的因数了,则称这个数为质数(or素数),否则合数。

比如:3=1*3,3就是一个质数;8=1*8=2*4,8就是个合数。

在c++里我们要判断质数or筛选质数,可以运用到以下几种方法:

(1) 试除法

若一个正整数N为合数,则在2~N-1里必定可以找到N的因数M。而我们可以知道:N的合数成双出现,不成双则为√N,如9=3*3(这里算3为一个合数),所以N若为合数,则在2~√N里一定可以找到一个N的因数。因此我们可以直接用for循环除以N来验证N是否为质数。

#include<iostream>
#include<cmath>
using namespace std;
int main()
{
	int n;
	cin>>n;
	for(int i=2;i<=sqrt(n);i++)
	{
		if(n%i==0)//取余
		{
			cout<<"No"<<endl;
			return 0;//找到了一个因数,则不必继续了
		}
	}
	cout<<"Yes"<<endl;
	return 0;
}

(2)Eratosthenes算法

看起来是个很高大上的名字(我都不会读),其实也是很简单的——但这一算法比较适用于要打出2~N之内的质数or统计有多少个,也就是全局性的问题。同样基于我们对质数的定义——一个正整数,除了1和它本身没有别的因数。所以有别的因数的都不是质数。

例——输出1~N以内所有的质数。

#include<iostream>
using namespace std;
int n;
bool v[110];
int main()
{
	cin>>n;
	for(int i=2;i<=n;i++)//2~n
	{
		if(v[i]) continue;//已经标记为合数了,继续循环不做操作
		cout<<i<<" ";
		for(int j=i;j<=n/i;j++)
		{
			v[i*j]=1;//找可以被i整除的n以内的数并标记
		}
	}
	return 0;
}

这一算法的时间复杂度为O(NloglogN),效率是十分高的,也十分常用。


(3)线性筛法

Eratosthenes算法就算优化也会重复标记合数,如12会被2和3重复标记,所以我们试试线性筛法。

这一筛法运用到了一个数学结论:任何一个合数都可以被分解为几个素数的乘积。如44=2*2*11等等。

考虑到上述的重复标记问题(加一个v的判断也可以,但为了优化还是介绍一下)

【p.s】最小质因数,顾名思义就是某合数的一个最小的为质数的因数。

#include<iostream>
using namespace std;
int n;
int v[1100];//在i下标处放i的最小质因数 
int prime[1100];//存放质数 
int m=0;
int main()
{
	cin>>n;
	for(int i=2;i<=n;i++)
	{
		if(v[i]==0)//不曾被标记上最小质因数,则i为质数。 
		{
			v[i]=i;
			//它本身作为一个因数,去筛将它作为最小质因数的数 
			prime[++m]=i;
		}
		
		for(int j=1;j<=m;j++)
		{
			if(prime[j]>v[i]||prime[j]>n/i) break;
			//i有比prime[j]更小的质因数,也就是已被标记 
			v[i*prime[j]]=prime[j];//没有则继续标记 
		}
	}
	
	for(int i=1;i<=m;i++)
	{
		cout<<prime[i]<<" ";
	}
	return 0;
}

这一算法中每个合数只会被它的最小质因数标记一次,时间复杂度为O(N)。


(4)质因数分解

例题:输入一大于一的整数n,将n分解为几个质数的乘积。

由上文所提到的:每个合数都可以被分解为几个质数的乘积,同时任意一个大于1的正整数都能唯一分解为有限个质数的乘积。所以我们可以尝试一下将试除法和Eratosthenes算法结合起来——扫描2~√N中的每个数d,若d能整除N,则存起来,并统计需要多少个d相乘。这里d天然保证了为质数,因为每个合数都可以分解为几个质数的乘积,在到这个合数之前n就应当已经被除尽了。

#include<iostream>
#include<cmath>
using namespace std;
int n;
int p[1100];//从小到大存放有哪些因数 
int c[1100];//存放第m个因数需要乘的次数 
int m=0;//计数 
int main()
{
	cin>>n;
	cout<<n;//格式而已,不用在意。毕竟操作过后n变了 
	for(int i=2;i<=sqrt(n);i++)
	{
		if(n%i==0)//找到因数 
		{
			p[++m]=i; 
			c[m]=0;
			while(n%i==0)//可以分解i则继续分解 
			{
				n/=i;
				c[m]++;//统计次数 
			}
		}
	}
	if(n>1)//最后n还是无法分解完,则为质数 
	{
		p[++m]=n;c[m]=1;
	}
	
	cout<<"=";//作为一个强迫症,为了格式而已…… 
	
	int num=0;//为了格式+1 
	for(int i=1;i<=m;i++)
	{
		for(int j=1;j<=c[i];j++)
		{
			if(num==0) cout<<p[i];
			else cout<<"×"<<p[i];
			num++;
		}
	}
	return 0;
}

毕竟基本上是纯数学知识,还是很好理解的~

后面的这个应用就比较多了。

2.欧拉函数

求1~n中与n互质(即最大公约数为1)的元素的个数,我们就要用到欧拉函数了。

这里我们可以用到容斥的思想,一共有n个数,有多少元素是n的约数(传送门:约数专题),那么减去就好了,剩下的就是与n互质的元素,,也就是:

\large n-(1+p1) * (1+p2)*...*(1+pt)

其中p为n的质因数,共有t个不同的质因数。

这个式子可以化简(太愚钝了化简不来直接上结论)得到的就是欧拉函数了:

\large \dpi{100} \large \varphi (n) = n * (1 - 1/p1)*(1-1/p2)*...*(1-1/pt)

这个函数在基础数论的各大公式推导中会用到,所以还是很重要的,也有一些常用性质,比如:

1)若n本身为素数,则n以内与它互质的数的个数为n - 1(也就是除了它自己)。

2)如果n为某一素数p的a次方,则有\large \varphi (p^{a}) = (p - 1) * p^{a -1}

3)欧拉函数为积性函数,即\large \varphi (a* b) = \varphi (a)*\varphi (b)。(2)的结论就是基于这条性质。

大约就是这些内容啦!

 

迎评:)

——End——

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值