【算法笔记】因数与质因数/求素数的两种线性复杂度的方法

1.质因数分解

质因数分解,把n从1到sqrt(n) 进行分解,即,for循环枚举i,若i能整除n,则用while循环不断地除n,边除边分解,标记.但在某些情况下,最后必然分解不完,因此最后进行以此特判,即最后再分解一步即可.

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int s=0,n,t;
    int ans[10000]={};
    cin>>n;t=n;
    for (int i=2;i*i<=n;i++)//相当与1-sqrt(n)枚举,这么做可以处理复杂的精度误差
        if (n%i==0)
        {
            while (n%i==0)
            {
                ans[++s]=i;//储存分解质因子的答案
                n=n/i;//对n除以i,知道不能除为止 
            }   
        }   
    if (n>2)  ans[++s]=n;//若最后没有分解完

    if (s==1) cout<<t<<'='<<ans[1];
    else if (s==2) cout<<t<<'='<<ans[1]<<'*'<<ans[2];
    else    for (int i=1;i<=s;i++)
            {
                if (i==1) cout<<t<<'='<<ans[1]<<'*';
                else  if (i==s) cout<<ans[s];
                else  cout<<ans[i]<<'*';
            } 
    //输出分解质因数的结果
    return 0; 
} 

2.求因数的个数

对于一个数:根据唯一分解定理去,其质因子个数的和是其指数+1再相乘.必然,我们同样可以用上述分解质因数的方法,求出质因数之后加上1,再和另外的变量相乘即可.同样,对于没能分解完的,质因子必然为1,在最后的结果中乘上(1+1)=2即可.

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,ans=1,t;
    cin>>n;t=n;
    for (int i=2;i*i<=t;i++)//相当与1-sqrt(n)枚举,这么做可以处理复杂的精度误差
        if (n%i==0)
        {
            int k=0;//用来统计质因子的个数 
            while (n%i==0)
            {
                k++;
                n=n/i;//对n除以i,知道不能除为止 
            }   
            ans=ans*(k+1);//指数+1再相乘 
        }   
    if (n>2)  ans=ans*2;//若最后没有分解完
    cout<<ans;
    return 0; 
} 

3.求因数之和

根据唯一分解定理,可得:因数之和=每一个 系数的每一个指数相加 再 相乘;
有点绕(毕竟不会打公式嘛) 举个例子: 求12的因数之和:1+2+3+4+6+12=28
分解,得:12=2*2*3=2^2*3^1 因数和为:(2^0+2^1+2^2)(3^0+3^1)=(1+2+4)(1+3)=4*7=28

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,ans=1;
    cin>>n;
    int t=n;
    for (int i=2;i*i<=t;i++)//相当与1-sqrt(n)枚举,这么做可以处理复杂的精度误差
        if (n%i==0)
        {
            int k=1,tmp=0;//用来统计质因子的个数 
            while (n%i==0)
            {
                tmp=tmp+k;//即存储系数与各个指数的和 
                n=n/i;//对n除以i,知道不能除为止 
                k*=i; 
            }   
            tmp=tmp+k;//处理没有加全的数字 
            ans=ans*tmp;//指数+1再相乘 
        }   
    if (n>2)  ans=ans*(1+n);//若最后没有分解完
    cout<<ans;
    return 0; 
} 

4.求阶乘质因子的特殊方法

我们要求的是n!中质因子m的个数,显然用n乘出来再用筛法或暴力运算会消耗大量的时间复杂度或数据范围溢出等错误,我们需要考虑用更加快速,高效,简洁的方式进行运算,据此,我们可得如下求n!中质因子m的个数s的公式:
s=(n/(m^1))+(n/(m^2))+(n/(m^3))……..(证明略)
以12为例:12!=1*2*3*4*5*6*7*8*9*10*11*12
质因子的个数是:1+2+1+3+1+2=10
运用公式,得:12/2+12/4+12/8=6+3+1=10.显然,运算次数更小,更高效

5.埃氏筛法:O(nloglogn)的高效选素数算法

假设要求1~100的素数:
2是素数, 删除2*2, 2*3, 2*4, …, 2*50
第一个没被删除的是3, 删除3*3, 3*4,3*5,…,3*33
第一个没被删除的是5, 删除5*5, 5*6, … 5*20
得到素数p时, 需要删除p*p, p*(p+1), …p*[n/p],
算法时间复杂度O(nloglogn),也就无限地接近于n了
每次做的时候,如果说当前做的数没有没有标记过,就表明它不是任何一个数的倍数,同样表示任何一个数都不是它的因数,故次数即为素数;当它去筛其它数字是,去标记素数表明被筛的数字就是素数,因为这个被筛的数有这个数作为因数,故为合数.这种方法几乎实在线性时间内完成的,故时间复杂度无限地接近于O(n),即O(nloglogn) 尽管不是特别理解是个复杂度,但凭借做二分的经验来看是很小的.

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n;
    int prime[10000]={};
    cin>>n;
    for (int i=2;i*i<=n;i++)
    {
        if (prime[i]==1) continue;//如果已经被标记位合数 
        for (int j=2*i;j<=n;j=j+i)//用j向上标记
            prime[j]=1;
    }   
    for (int i=2;i<=n;i++)//输出最终结果
        if (prime[i]==0) 
            cout<<i<<' ';
    return 0;
} 

6. 最快的求素数方法:欧拉筛法——O(n)的线性复杂度

欧拉筛法,又称线性筛法,是埃氏筛法的加强版.
尽管埃氏筛法无限接近与线性复杂度,loglogn的时间复杂度的小号仍然是不可避免的.不难发现,这loglogn的消耗是在于在向上筛的时候增加了一些无用的判断,那么问题来了,如何优化:
筛的过程增加了许多的冗余运算,因此,我们必须要用一个数的最大因数与最小素因数进行筛法运算,即:一个数必然等于 最大因数*最小素因数.至于怎么进行筛法运算,具体如下:
(注:所求为20内的素数)
2:筛掉4
3:筛掉6,9
4:筛掉8
5:筛掉10,15,25
6:筛掉12
7:筛掉14,21,35,49
8:筛掉16
9:筛掉18,27
10:筛掉20
11:筛掉22
12: 筛掉24
13: 筛掉26
14: 筛掉28
15: 筛掉30
或许你会发现和埃氏筛法的不同,例如:
2不筛6,因为这个算法是合数用枚举的最大因数乘上最小的素因数而进行筛选的,但是枚举的却是最小素因数,显然要枚举到3;又例如,4不筛12,因为12=最小素因数*最大因数=2*6而并非3*4所以必然不选择.且是由枚举的数通过已经求出的素数而得知的.

这里有一个关键的句子:if (i%prime[j]==0) break
这是什么含义呢?
即:当这个数去乘上前面的素数的时候,这个素数必然是这个数的素因数,那么再乘上下一个数字的时候,乘上的素因数是第二大的,必然不满足:由最小素因数乘上最大因数这个算法原理.

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,cnt=0;
    int prime[10000]={};//用来存储素数 
    int Vis[10000]={};//用来判断是否是素数
    cin>>n;
    for (int i=2;i<=n;i++)
    {
        if (Vis[i]==0) prime[++cnt]=i;//如果没有被标记过 
        for (int j=1;j<=cnt&&i*prime[j]<=n;j++)//用i去乘上每一个已经求过的素数 
        {
            Vis[i*prime[j]]=1;//标记素数 
            if (i%prime[j]==0) break;//下面再解释 
        }
    }
    for (int i=2;i<=n;i++) 
        if (Vis[i]==0) cout<<i<<' ';
    return 0; 
} 

猜你喜欢

转载自blog.csdn.net/ronaldo7_zyb/article/details/80954209
今日推荐