算法基础4.1数学——质数,约数

质数

质数定义:一个大于1的自然数,除了1和它本身外,不能被其他自然数整除,换句话说就是该数除了1和它本身以外不再有其他的因数,这个数就是质数。

1.质数的判定--试除法

给定一个数 x,判断 x 是否为质数:

用 x 除以 2 ~ x - 1 中的每个数,如果出现了余数为 0,则这个数不是质数,如果没有出现余数为 0,则这个数是质数。

bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
            return false;
    return true;
}         时间On

优化:

一个数的因数都是成对出现的:例如12的因数有3和4,2和6
所以我们可以只枚举较小的那一个,即根下n,假设较小的为d,较大的为n/d;

有性质:若 d|n,则 n/d|n。

例题:试除法判定质数

给定 n个正整数 ai,判定每个数是否是质数。

输入格式

第一行包含整数 n。

接下来 n行,每行包含一个正整数 ai。

输出格式

共 n 行,其中第 i行输出第 i 个正整数 ai 是否为质数,是则输出 Yes,否则输出 No

数据范围

1≤n≤100,
1≤ai≤2的31次方-1

输入样例:
2
2
6
输出样例:
Yes
No

#include<bits/stdc++.h>
using namespace std;

bool is_prime(int n){
    if(n<2)  return false;
    for(int i=2;i<=n/i;i++){
        if(n%i==0) return false;
    }
    return true;
}

int main(){
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        int a;
        cin>>a;
        if(is_prime(a)) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }     时间O根号n
}

作者:TaoZex
链接:https://www.acwing.com/solution/content/3368/
来源:AcWing

2.分解质因数--试除法

试除法分解质因数:

n=pα11…pαkk
void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)     //若此条件成立,i一定是质数
        {
            int s = 0;      //s为次数
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;   //单独处理,唯一一个大于sqrt(n)的质因子
    cout << endl;
}
注:由于 n不包含任何从 2到 i−1 之间的质因子(已经被除干净了),若x % i = 0成立,i不包含其中的所有质因子,故i一定是质数;
n中最多只包含一个大于sqrt(n)的质因子;
当n = 2^k时,时间复杂度为O(log n),而最坏时间复杂度为O(sqrt(n)),平均情况是介于两者之间。

x 的质因子最多只包含一个大于 根号x 的质数。如果有两个,这两个因子的乘积就会大于 x,矛盾。

i 从 2 遍历到 根号x。 用 x / i,如果余数为 0,则 i 是一个质因子。

s 表示质因子 i 的指数,x /= i 为 0,则 s++, x = x / i 。

最后检查是否有大于 根号x 的质因子,如果有,输出。

例题:分解质因数

给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个正整数 ai。

输出格式

对于每个正整数 ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。

每个正整数的质因数全部输出完毕后,输出一个空行。

数据范围

1≤n≤100,
2≤ai≤2×10的9次方

输入样例:
2
6
8
输出样例:
2 1
3 1

2 3

#include <iostream>
#include <algorithm>

using namespace std;

void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )//i <= x / i:防止越界,速度大于 i < sqrt(x)
        if (x % i == 0)//i为底数
        {
            int s = 0;//s为指数
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;//输出
        }
    if (x > 1) cout << x << ' ' << 1 << endl;//如果x还有剩余,单独处理
    cout << endl;
}

int main()
{
    int n;
    cin >> n;
    while (n -- )
    {
        int x;
        cin >> x;
        divide(x);
    }

    return 0;
}       时间复杂度可能为根n

作者:Hasity
链接:https://www.acwing.com/solution/content/27448/
来源:AcWing

3.筛质数

1.朴素筛法

不断筛掉所有数的倍数

1.最普通的筛法 O(nlogn)
void get_primes2(){
    for(int i=2;i<=n;i++){

        if(!st[i]) primes[cnt++]=i;//把素数存起来
        for(int j=i;j<=n;j+=i){//不管是合数还是质数,都用来筛掉后面它的倍数
            st[j]=true;
        }
    }
}

2.优化版就是只删质数的倍数

质数定理:1-n中只有 n/ logn个质数

诶氏筛法 O(nloglogn)
void get_primes1(){
    for(int i=2;i<=n;i++){
        if(!st[i]){
            primes[cnt++]=i;
            for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
        }
    }
}

3.线性筛法

n 只会被它的最小质因子筛掉:

i % primes[j] == 0:primes[j]一定是i的最小质因子,primes[j]一定是primes[j] * i的最小质因子
i % primes[j] != 0:primes[j]一定小于i的最小质因子,primes[j]也一定是primes[j] * i的最小质因子
对于一个合数 x,假设 primes[j] 是 x 的最小质因子,当 i 枚举到 x / primes[j] 时,将会筛掉,每个合数只会被筛掉一次。

int primes[N], cnt;                             // primes[]存储所有素数
bool st[N];                                     // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;      // primes[j]一定是i的最小质因子
        }
    }
}

void get_primes(){
    //外层从2~n迭代,因为这毕竟算的是1~n中质数的个数,而不是某个数是不是质数的判定
    for(int i=2;i<=n;i++){
        if(!st[i]) primes[cnt++]=i;
        for(int j=0;primes[j]<=n/i;j++){//primes[j]<=n/i:变形一下得到——primes[j]*i<=n,把大于n的合数都筛了就
        //没啥意义了
            st[primes[j]*i]=true;//用最小质因子去筛合数

            //1)当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,那么只可能是此时的primes[j]<i的
            //最小质因子,所以primes[j]*i的最小质因子就是primes[j];
            //2)当有i%primes[j]==0时,说明i的最小质因子是primes[j],因此primes[j]*i的最小质因子也就应该是
            //prime[j],之后接着用st[primes[j+1]*i]=true去筛合数时,就不是用最小质因子去更新了,因为i有最小
            //质因子primes[j]<primes[j+1],此时的primes[j+1]不是primes[j+1]*i的最小质因子,此时就应该
            //退出循环,避免之后重复进行筛选。
            if(i%primes[j]==0) break;
        }
    }

}

例题:筛质数

给定一个正整数 n,请你求出 1∼n中质数的个数。

输入格式

共一行,包含整数 n。

输出格式

共一行,包含一个整数,表示 1∼n 中质数的个数。

数据范围

1≤n≤10的6次方

输入样例:
8
输出样例:
4

#include <iostream>
#include <algorithm>

using namespace std;

const int N= 1000010;

int primes[N], cnt;
bool st[N];

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

int main()
{
    int n;
    cin >> n;

    get_primes(n);

    cout << cnt << endl;

    return 0;
}

作者:yxc
链接:https://www.acwing.com/activity/content/code/content/49975/
来源:AcWing

约数

1.试除法求约数

什么是约数:如果一个数a除以另一个数b的余数为0,即 a%b == 0, 则b是a的约数。

如何求一个数x的所有约数:

用 x 除以 1 到 x 的所有数,如果余数是0,则把除数加到答案中。
可以优化吗?如果 a / b = c···0,则一定有 a % c = b····0。所以一个数 x 的约数肯定是成对存在的,对称轴是 根号x。
因此,只需要用 x 除以 1 到 根号x 之间的数,如果余数是0,则把除数以及x / 除数加到答案中。

作者:Hasity
链接:https://www.acwing.com/solution/content/148960/
来源:AcWing

例题:试除法求约数

给定 n 个正整数 ai,对于每个整数 ai,请你按照从小到大的顺序输出它的所有约数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式

输出共 n行,其中第 i行输出第 i 个整数 ai 的所有约数。

数据范围

1≤n≤100,
1≤ai≤2×10的9次方

输入样例:
2
6
8
输出样例:
1 2 3 6 
1 2 4 8 


#include <iostream>
#include <vector>   //vector存所有约数
#include <algorithm>
using namespace std;
int main()
{
    int T;
    cin >> T;
    while(T--)
    {
        int n;
        cin >> n;
        vector<int> res; //存答案的数组
        //因为约数成对出现,所以只需要循环到根号x
        // 不要是用 i *i <= n,因为可能溢出
        for(int i = 1; i <= n /i; i++)
        {
            if(n % i == 0)
            {
                res.push_back(i);
                //如果i * i = x,添加i即可,不用添加 x / i
                if(n / i != i)   //有可能n是i的平方,避免重复
                    res.push_back(n / i);
            }
        }
        sort(res.begin(), res.end());  //最后排序
        for(auto x : res) cout << x << " ";
        cout << endl;

    }
}       时间复杂度nloga,一定要不断地算时间复杂度

2.约数个数

一个数的约数是由这个数的几个质因子相乘得到的。

例如:12 的质因子有 2,3。12的约数有:1,2,3,4,6,12。

约数1 是由 0 个 2, 0 个3相乘得到的。
约数2 是由 1 个 2, 0 个3相乘得到的。
约数3 是由 0 个 2, 1 个3相乘得到的。
约数4 是由 2 个 2, 0 个3相乘得到的。
约数6 是由 1 个 2, 1 个3相乘得到的。
约数12 是由 2 个 2, 1 个3相乘得到的。
12 可以分解为:2^2*3^1。所以2可以取 0 ~ 2个,3种取法。3可以取 0~1 个,2种取法。12的约数一共:2 * 3 = 6个。

也就是:把一个数N 写成:N = (p1^x1^)(p^x2)(p3^x3)…(pk^xk),其中pi为质数。则N的约数个数为:(x1+1)(x2+1)(x3+1)…(xk+1)

N=ans=d=pα11∗pα22∗⋯∗pαkk(α1+1)(α2+1)…(αk+1)

因为任何一个约数d可以表示成

pβ11∗pβ22∗⋯∗pβkk,0≤βi≤αi

每一项的βi如果不同,那么约数d就不相同(算数基本定理,每个数的因式分解是唯一的)

所以n的约数就跟βi的选法是一一对应的

β1一共有0∼α1种选法

β2一共有0∼α2种选法…

βk一共有0∼αk种选法乘法原理,

一共有ans个约数

例题:约数个数

给定 n个正整数 ai,请你输出这些数的乘积的约数个数,答案对 10的9次方+7取模。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数 ai。

输出格式

输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7109+7 取模。

数据范围

1≤n≤100,
1≤ai≤2×10的9次方

输入样例:
3
2
6
8
输出样例:
12

#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int mod = 1e9+7 ;
int main()
{
    int T; 
    cin >> T;
    unordered_map<int, int> h;
    while(T--)
    {
        int n; cin >> n;
        //依次求出指数
        for(int i = 2; i <= n / i; i++)
        {
            while(n % i == 0)
            {
                //指数+1
                h[i] ++;
                n = n / i;
            }
        }
        //如果有剩余,也是一个质因子
        if(n > 1) h[n]++;
    }

    long long  res = 1;
    for(auto iter = h.begin(); iter != h.end(); iter++)
    {
        //res = (x1+1)(x2+1)(x3+1)…(xk+1)
        res = res * (iter->second + 1) % mod ;
    }
    cout << res;
}

作者:Hasity
链接:https://www.acwing.com/solution/content/148964/
来源:AcWing

3.约数之和

由算术基本定理得任意一个数A=pk11×pk22×…×pknn
则A的约数之和为(p01+p11+…+pk11)×(p02+p12+…+pk22)×…×(p0n+p1n+…+pknn)
,展开可以得到(k1+1)(k2+1)…(kn+1)个数之和,也就是A的约数之和。

下面的问题是如何求S(n)=x0+x1+x2+…+xn,S(n)=1+x×(x0+x1+…+xn−1)=1+S(n−1)×x
,边界情况S(0)=1,所以可以令t=1,乘以x再加上1得到S(1),再乘以x并加上1得到S(2),执行n次循环得到S(n)。

作者:yxc的小迷妹
链接:https://www.acwing.com/solution/content/145781/
来源:AcWing

例题:约数之和

给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 10的9次方+7 取模。

输入格式

第一行包含整数 n。

接下来 n行,每行包含一个整数 ai。

输出格式

输出一个整数,表示所给正整数的乘积的约数之和,答案需对 10的9次方取模。

数据范围

1≤n≤100,
1≤ai≤2×10的9次方

输入样例:
3
2
6
8
输出样例:
252

#include <iostream>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7; //最后取模用的
int main(){
    int n;
    scanf("%d",&n);
    unordered_map <int,int> primes; //存x这个数的所有约数
    while(n --){ // 分解质因数并存在primes数组里
        int x;
        scanf("%d",&x);
        for(int i = 2;i <= x / i;i ++){ //具体执行过程见解析①
            while(x % i == 0){ //一直除到不能再整除为止
                x /= i; //把x除掉i
                primes[i] ++; //把第i个数的指数+1
            }
        }
        if(x > 1) primes[x] ++; //如果x还是大于1的,说明x现在是原本x的因数,把x的指数+1
    }
    long long res = 1; //约数的和
    // 这里其实就是按照约数之和的公式求出res
    for(auto prime : primes){ //遍历一遍primes数组
        int p = prime.first,a = prime.second; //p为底数,a为指数
        long long t = 1; //见后面解析②
        while(a --) t = (t * p + 1) % mod;
        res = res * t % mod;
    }
    printf("%lld",res); //输出答案
    return 0;
}

作者:冰中月
链接:https://www.acwing.com/solution/content/113135/
来源:AcWing

4.最大公约数(欧几里得算法,辗转相除法)

最大公约数(Greatest Common Divisor)指两个或多个整数共有约数中最大的一个。也称最大公因数、最大公因子,a, b的最大公约数记为(a,b),同样的,a,b,c的最大 公约数记为(a,b,c),多个 整数的最大公约数也有同样的记号。求最大公约数有多种 方法,常见的有 质因数分解法、 短除法、 辗转相除法、 更相减损法。

若d|a,d|b,则d|(ax + by)。有结论:(a,b) = (b,a mod b)。

证明:

amodb=a−⌊a/b⌋∗b=a−c∗b
(其中c为一整数)。则只需证明(a,b) = (b,a - c * b)即可。

对于左边任意一个公约数,由d|a,d|b,故d|a - c * b;
对于右边任意一个公约数,由d|a - c * b,又d|b,则有d|a - c * b + c * b,即d|a。
则左边的最大公约数 = 右边的最大公约数,故有(a,b) = (b,a mod b)。

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

例题:最大公约数

给定 n对正整数 ai,bi,请你求出每对数的最大公约数。

输入格式

第一行包含整数 n。

接下来 n 行,每行包含一个整数对 ai,bi。

输出格式

输出共 n行,每行输出一个整数对的最大公约数。

数据范围

1≤n≤10的5次方,
1≤ai,bi≤2×10的9次方

输入样例:
2
3 6
4 6
输出样例:
3
2

#include<bits/stdc++.h>
using namespace std;
/*
求两个正整数 a 和 b 的 最大公约数 d
则有 gcd(a,b) = gcd(b,a%b)
证明:
    设a%b = a - k*b 其中k = a/b(向下取整)
    若d是(a,b)的公约数 则知 d|a 且 d|b 则易知 d|a-k*b 故d也是(b,a%b) 的公约数
    若d是(b,a%b)的公约数 则知 d|b 且 d|a-k*b 则 d|a-k*b+k*b = d|a 故而d同时整除a和b 所以d也是(a,b)的公约数
    因此(a,b)的公约数集合和(b,a%b)的公约数集合相同 所以他们的最大公约数也相同 证毕#
*/
int gcd(int a, int b){
    return b ? gcd(b,a%b):a; 
}

int main(){
    int n,a,b;
    cin>>n;
    while(n--) cin>>a>>b,cout<<gcd(a,b)<<endl;
    return 0;
}

常算时间复杂度

end——————————————————————————————————————————

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值