欧拉筛及其扩展应用

首先我们先来回顾一下判定质数(试除法):

 

 Tips:如果i*i爆int,则改为i<=x/i或i<=sqrt(x)。

下面讲欧拉筛之前我们先来看看朴素(筛法)求质数的案例吧!

如果只对一个整数进行素性判断,那么普通的判断素数的方法(复杂度为O(√n))也足够了,但如果对一系列数进行判断,那么复杂度太大,超时的可能性很大,这时就需要一个更高效的算法来求一定范围内的素数了,埃氏筛(复杂度为O(nloglogn))就可以很好地胜任这项工作。

埃氏筛的实现原理:

例如(打表)求0~1000以内的素数:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
bool vis[N];
int p[N];
int idx,n;
void get_primes(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]){
			p[++idx]=i;//是素数,存起来 
			for(int j=i*i;j<=n;j+=i)//将素数i的倍数全部标记为合数
			vis[j]=true;
		}
	}
}
int main()
{
	cin>>n;//如输入1000即打印0~1000以内的素数 
	get_primes(n);
	for(int i=1;i<=idx;i++)
	cout<<p[i]<<",";
    return 0;
}

结果: 

2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,

关键点解释:标记素数的倍数时,没有从j*2开始,从j*j开始,如果从j*m(m<j)开始,求得m的某个素因子时已经标记过,没必要再标记。 

例如,已知素数3,3*2=6,但6在i=2,2*3时就已经标记。

优化:如果数很大很大,那么就欧拉筛(复杂度O(n)故也称线性筛)来接替埃氏筛的这份工作了。

欧拉筛法是埃氏筛法的优化,非素数(合数)仅被最小素因子标记一次。在埃氏筛法中,对于一个非素数,它会被它的素因子标记多次(即重复了vis[j]=true),例如,30=2*15=3*10=5*6,在得到素数2,3,5时均被标记一次,数大时更会标记很多次,做了很多无用功。

欧拉筛:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
bool vis[N];
int p[N];
int idx,n;
void get_primes(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i])
    	p[++idx]=i;
    	for(int j=1;j<=idx&&i*p[j]<=n;j++)
		{//取出每个素数,并把它的倍数 i*p[j] 标记为合数 
			vis[i*p[j]]=true;
			if(i%p[j]==0) break;
		}
	}
}
int main()
{
	cin>>n;//如输入1000即打印0~1000以内的素数 
	get_primes(n);
	for(int i=1;i<=idx;i++)
	cout<<p[i]<<",";
    return 0;
}

欧拉筛思想的核心是保证每个合数只被这个合数最小的质因子筛除,而且只筛一次。

关键点解释:break的原因->举个例子 :i = 4 ,j = 1,p[j] = 2,4*2=8,如果不退出循环,接着4*3=12,那么12就会被3给筛除,但3并不是12的最小质因子,而12其实会在i=6,j=1时被2给筛除(6*2=12)。

难道欧拉筛只能用来求质数吗?答案是否定的!

欧拉筛除了可以用来求1~n的所有质数,还可以

求1~n的欧拉函数

求1~n的莫比乌斯函数

求1~n的约数个数和

求1~n的约数和

......

如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

下面我们再来看怎么求欧拉函数!

首先我们得先知道什么是欧拉函数(定义👇)

也可以变形成下面公式,不过编程中用的是上面的公式(因计算机中的 / 是整除)

 求某个数的欧拉函数:

(分解质因数的复杂度为O(√n),故求某个数的欧拉函数的复杂度也是O(√n))

int phi(int x)//求x的欧拉函数
{
    int res = x;
    for (int i = 2; i <= x / i; i ++ )//分解质因数
        if (x % i == 0)
        {
            res = res / i * (i - 1);//这样处理能保证res%i==0所以 / 不会出现误差
            while (x % i == 0) x /= i;
        }
    if (x > 1) res = res / x * (x - 1);//如果还有大于1的质因子

    return res;
}

 那如果是求1~n的欧拉函数呢,总不能一个个去求吧,这样复杂度O(n√n)太大了。

既然求欧拉函数要用到质数,那么能不能在求出质数的同时求欧拉函数呢?答案是肯定的!

求1~n的欧拉函数:(运用欧拉筛法复杂度为O(n))

int primes[N], cnt;  // primes[]存储所有素数
int euler[N];        // 存储每个数的欧拉函数
bool st[N];         // st[x]存储x是否被筛掉


void get_eulers(int n)
{
    euler[1] = 1;//初始化
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i])
        {
            primes[cnt ++ ] = i;
            euler[i] = i - 1;//如果i是质数,那么1~i-1都与它互质
        }
        for (int j = 0; j<cnt&&i*primes[j] <= n; j ++ )
        {
            int t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0)//primes[j]是i的最小质因数也是t的最小质因数
            {
                euler[t] = euler[i] * primes[j];
                break;
            }//当i%primes[j]!=0时,primes[j]不是i的质因数,但却是t的最小质因数
            euler[t] = euler[i] * (primes[j] - 1);
            //由euler[t]=euler[i]/primes[j]*(primes[j] - 1)*primes[j]化简得↑
        }
    }
}

~~~分界线~~~

概念补充:(不清楚或者忘了可以看看当复习)

合数是指在大于1的整数中除了能被1和本身整除外,还能被其他数(0除外)整除的数。与之相对的是质数,而1既不属于质数也不属于合数。最小的合数是4。

质因数(素因数或质因子)在数论里是指能整除给定正整数的质数。除了1以外,两个没有其他共同质因子的正整数称为互质。

互质数 公因数只有1的两个非零自然数,叫做互质数。(gcd(a,b)==1)

        互质数的常用相关定理:

        两个不同的质数为互质数;

        1和任何自然数互质;

        不含相同质因数的两个合数互质;

        任何相邻的两个数互质。

易混:

整除与被整除:若整数 b 除以非零整数 a ,商为整数,且余数为 0 ,则 b 能被 a 整除(或者说 a 能整除 b)。其中 b 为被除数,a 为除数。a 称作 b 的约数(因数),b 称作 a 的倍数。
除和除以:2
6 即 “6 ÷ 2“;6 除以 2 即 ”6 ÷ 2“。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北边一颗小星星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值