素数筛,线性筛(欧拉筛),莫比乌斯函数筛,前n个数的约数个数筛

问题:给出一个数n,输出1~n之间的素数

素数筛

  1. 埃拉托斯特尼筛法
  • 每次消去2、3、4、5 、6 、、、、、、的倍数,直到没有可消的为止,剩下的数字则为素数;
    每次考虑消去的第一个数为i*i,因为(i-1)*i已经在i-1为基数,i为倍数的时候已经判断了;以此类推,当i的倍数小于i时都被判断了,于是只要判断i的倍数大于等于i的时候
    时间复杂度为O(n*logn);空间复杂度O(n)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1000+10;
int vis[M];
int cnt;
void getprim( )
{
   for(int i=2;i*i<=M;i++)
   {
       int k=i;
       for(int j=i*i;j<=M;j=i*k)
       {
           vis[j]=1;
           k++;
       }
   }
}
int main( )
{
   memset(vis,0,sizeof(vis));
   vis[1]=1;
   getprim( );
   for(int i=1;i<M;i++)
   {
       if(vis[i]==0)
       {
           cout<<i<<endl;
       }
   }
   return 0;
}
  1. 欧拉筛法
  • 在埃拉托斯特尼筛算法中有很多数都重复操作vis[j]=1,因为2的倍数如16,20,24已经判断了,但是在4的倍数也有16,20,24然后又判断一次所以就有重复的
    欧拉筛法
    每次用已筛出来的质数去筛更大的数,每个合数只被它最小的质因子筛掉。
    试想,如果2*6筛了12之后还没break,而是用3*6筛掉18,那么18还会被2*9筛一次,就重复了而根本原因就是6有2这个因子,而3*6筛掉的数一定也有2这个因子,3*6这个数应该被2这个因子筛掉,而不是3
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1000+10;
int vis[M];
int prim[M];
int cnt;
void getprim( )
{
   memset(vis,0,sizeof(vis));
   memset(prim,0,sizeof(prim));
   for(int i=2;i<=M;i++)
   {
       if(!vis[i])//在2~i-1之间没有标记,意味着没有它的因数,那么它是质数
       {
           prim[cnt++]=i;
       }
       for(int j=0;j<cnt&&i*prim[j]<=M;j++)
       {
           vis[i*prim[j]]=1;//剔除每个质数的i倍的那个数
           if(i%prim[j]==0)//保证每个合数只被它最小的质因子剔除
           {
               break;
           }
       }
   }
}
int main( )
{
   cnt=0;
   getprim( );
   for(int i=0;i<cnt;i++)
   {
       cout<<prim[i]<<endl;
   }
   return 0;
}

问题:给出一个数n,输出1~n之间的每个数x的欧拉函数φ(x)

欧拉筛

  • 根据欧拉函数的相关定理
    phi[1]=1;phi[p]=p-1其中p为质数
    phi[ab]=phi[a]*phi[b]其中a,b互质

  • φ(x)=x*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1000+10;
int vis[M];
int prim[M];
int phi[M];
int cnt;
void getprim( )
{
    memset(vis,0,sizeof(vis));
    memset(prim,0,sizeof(prim));
    memset(phi,0,sizeof(phi));
    phi[1]=1;
    for(int i=2;i<=M;i++)
    {
        if(!vis[i])
        {
            prim[cnt++]=i;
            phi[i]=i-1;
        }
        for(int j=0;j<cnt&&i*prim[j]<=M;j++)
        {
            vis[i*prim[j]]=1;
            if(i%prim[j]==0)
            {
                phi[i*prim[j]]=phi[i]*prim[j];
                break;
            }
            else
            {
                phi[i*prim[j]]=phi[i]*(prim[j]-1);
            }
            
        }
    }
}
int main( )
{
    cnt=0;
    getprim( );
    // for(int i=0;i<cnt;i++)
    // {
    //     cout<<prim[i]<<endl;
    // }
    // cout<<endl;
    for(int i=1;i<M;i++)
    {
        cout<<phi[i]<<endl;
    }
    return 0;i
}
  • i\%prim[j]==0意味着prim[j]这个质数是i的因子

因为φ(i)=i*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)
所以φ(i*prim[j])=i*prim[j]*(1-1/p_1)*(1-1/p_2)*...*(1-1/p_k)=φ(i)*prim[j]

因为i*prim[j]分解出来的p一定与i分解出来的p相同(其中p==prim[j]的指数不一样)
所以phi[i*prim[j]]=prim[j]*phi[i]

  • i\%prim[j]!=0意味着iprim[j]互质
    根据欧拉函数的积性函数
    phi[i*prim[j]]=phi[i]*(prim[j]-1)

莫比乌斯函数筛

  • μ(n) ——莫比乌斯函数,关于非平方数的质因子数目。
    μ[n]=\begin{cases} 1 \ \ \ \ \ \ n==1\\ (-1)^k\ \ \ \ \ \ \ n没有平方数因数且n=p_1*p_2*...*p_k\\ 0 \ \ \ \ \ \ \ \ \ \ n有大于1的平方数因数\end{cases}

1)莫比乌斯函数μ(n)的定义域是N;
2)μ(1)=1;
3)当n存在平方因子时,μ(n)=0;
4)当n是素数或奇数个不同素数之积时,μ(n)=-1;
5)当n是偶数个不同素数之积时,μ(n)=1。

  • 这个做起来比欧拉函数容易,在过程上,若i为素数则μ[i] = -1,若i % prim[j] == 0,则μ[i * prim[j]] = 0,显然prim[j]就是它的平方因子,否则μ[i * prim[j]] = -μ[i],因为多了一个质因子
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1e3+10;
int mob[M];
int prim[M];
int vis[M];
int cnt;
void getmob( )
{
    memset(vis,0,sizeof(vis));
    memset(prim,0,sizeof(prim));
    memset(mob,0,sizeof(mob));
    mob[1]=1;
    for(int i=2;i<M;i++)
    {
        if(!vis[i])//质数
        {
            prim[cnt++]=i;
            mob[i]=-1;//质数为-1
        }
        for(int j=0;j<cnt&&i*prim[j]<M;j++)
        {
            vis[i*prim[j]]=1;
            if(i%prim[j]==0)
            {
                mob[i*prim[j]]=0;//i%prim[j]==0,意味着i中有因子prim[j],所以i*prim[j]中有平方因子prim[j]
                break;
            }
            else
            {
                mob[i*prim[j]]=-mob[i];
            }
            
        }
    }
}
int main( )
{
    cnt=0;
    getmob( );
    for(int i=1;i<M;i++)
    {
        cout<<mob[i]<<endl;
    }
    return 0;
}

前n个数的约数个数筛

  1. 一个大于1的正整数N都有一个唯一分解定理:
    N=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n} (p_i是质数)
  2. 算术基本定理中,根据拆分后的质因子的指数,我们可以求出每个N的约数的个数。所以N的约数个数可以表示为:
    d(N)=(1+k_1)*(1+k_2)*...*(1+k_n)
  3. 根据这个式子,我们可以用线性筛去筛出当前 N 的约数个数。
    筛的过程中,我们需要保存下最小质因子的个数。
证明:

首先规定:
d[i]表示i的约数个数
num[i]表示i的最小质因子的个数(指数)
prim[j]表示第j个质数

  • 情况1:
    i是一个质数,那么d[i]=(1+1)=2(约数为1和自己本身i)
    只有一个质因子就是自身(i) ,所以num[i]=1
  • 情况2:
    i\%prim[j]!=0
    i\%prim[j]!=0说明i中没有prim[j]这个质因子,但是i*prim[j]有这个质因子,i的唯一分解定理i=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}其中(p_i!=prim[j])
    所以i*prim[j]=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}*(prim[j])^1
    d[i]=(1+k_1)*(1+k_2)*...*(1+k_n)
    所以d[i*prim[j]]=(1+k_1)*(1+k_2)*...*(1+k_n)*(1+1)
    所以d[i*prim[j]]=d[i]*2
    i*prim[j]的最小质因子是什么呢!因为prim[j]是从小到大枚举并且在线性筛中每个只会被自己最小的质因子剔除
    所以i*prim[j]的最小质因子就是prim[j]
    所以num[i*prim[j]]=1

情况3:
i\%prin[j]==0
i\%prim[j]==0说明i中有prim[j]质因子,其中prim[j]肯定是i的最小质因子,因为prim是从小到大枚举
i=p_1^{k_1}*p_2^{k_2}*...*p_n^{k_n}
那么i*prim[j]=p_1^{k_1+1}*p_2^{k_2}*...*p_n^{k_n}

d[i]=(1+k_1)*(1+k_2)*...*(1+k_n)
d[i*prim[j]]=(1+k_1+1)*(1+k_2)*...*(1+k_n)

num[i*prim[j]]=num[i]+1
所以
d[i*prim[j]]=d[i]/(1+num[i])*(1+num[i*prim[j]])

参考二连
「参考」 「参考」

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int M=1e3+10;
int d[M];
int prim[M];
int num[M];//i的最小质因子的个数(指数)
int vis[M];
int cnt;
void getd( )
{
    memset(vis,0,sizeof(vis));
    memset(prim,0,sizeof(prim));
    memset(d,0,sizeof(d));
    memset(num,0,sizeof(num));
    d[1]=1;
    for(int i=2;i<M;i++)
    {
        if(!vis[i])
        {
            prim[cnt++]=i;
            d[i]=2;
            num[i]=1;
        }
        for(int j=0;j<cnt&&i*prim[j]<M;j++)
        {
            vis[i*prim[j]]=1;
            if(i%prim[j]==0)
            {
                num[i*prim[j]]=num[i]+1;
                d[i*prim[j]]=d[i]/(num[i]+1)*(num[i*prim[j]]+1);
                break;
            }
            else
            {
                num[i*prim[j]]=1;
                d[i*prim[j]]=d[i]*2;
            }
        }
    }
}
int main( )
{
    cnt=0;
    getd( );
    for(int i=1;i<M;i++)
    {
        cout<<d[i]<<endl;
    }
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,539评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,911评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,337评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,723评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,795评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,762评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,742评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,508评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,954评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,247评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,404评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,104评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,736评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,352评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,557评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,371评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,292评论 2 352

推荐阅读更多精彩内容

  • Java经典问题算法大全 /*【程序1】 题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子...
    赵宇_阿特奇阅读 1,858评论 0 2
  • 50道经典Java编程练习题,将数学思维运用到编程中来。抱歉哈找不到文章的原贴了,有冒犯的麻烦知会声哈~ 1.指数...
    OSET我要编程阅读 6,960评论 0 9
  • 什么是素数 素数又称质数,是指大于1的自然数中,除了1和它本身,不能被其它自然数整除的数字。1被定义为非素数。大于...
    程点阅读 7,162评论 1 7
  • 提示:别用莫比乌斯反演公式,会炸的 只需要记住: [gcd(i,j)=1]=\sum_{d|gcd(i,j)}\m...
    An_Account阅读 1,906评论 0 0
  • C语言的学习要从基础开始,这里是100个经典的算法-1C语言的学习要从基础开始,这里是100个经典的 算法 题目:...
    Poison_19ce阅读 1,134评论 0 0