【蓝桥杯算法笔记】数论

J.数论

基础知识

一、同余

1. a同余2(mod n)  a%n==2
2. (a+b)%n => (a%n+b%n)%n
3. (a*b)%n => (a%n)*(b%n)%n

二、欧几里得算法(辗转相除法)

1.作用:求最大公约数
2.表示:最大公约数(a,b) gcd(a,b) 最小公倍数 [a,b] lcm(a,b)
3.理论基础:(a,b)=(b,a mod b)

a和b的最大公约数等于b和a模上b的最大公约数

4.时间复杂度:O(logn)
//记住(a,b)=(b,a mod b),可以8和2为例
int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;  //如果b不是0的话,返回b和a模上b的最大公约数
    							   //否则,若b是0的话,返回a。因为0和一个数的最大公约数是他本身
}
5.最小公倍数:lcm(a,b)=(a*b)/gcd(a,b)

两个数的最小公倍数是这两个数的乘积除以他们的最大公约数

理解的话可以4 和 2 为例。

6.C++自带内置函数求最大公约数函数:__gcd(a,b)

三、算术基本原理(因式分解定理)

1.作用:是所有数论的基础。
2.内容:
N = P1^a1 * P2^a2 ** Pn^an (pi为质数,ai>0)
则N的约数个数为(a1+1)(a2+1)(an+1)

假设N的一个约数为D

D = P1^b1 * P2^b2 ** Pn^bn
其中bi可以取到0,范围是0<= bi <= ai

因为只有和N的质因数一一对应一定能得到约数
因为bi可以从0取到ai,那么每一个bi就有(ai+1)种选法

约数的个数就是每个bi对应多少种选法相乘
即约数个数就为(a1+1)(a2+1)(an+1)

约数之和S = (1+p1+p1^2++p1^a1)(1+p2+p2^2++p2^a2)(1+pn+pn^2++pn^an)

四、线性筛法求素数(筛素数)

1.作用:可以在 O(n) 的时间复杂度内求出 1∼n 之间的所有质数以及每个数的最小质因子。
2.时间复杂度:O(n)
3.思想:
4.代码模板:
//一般需要三个变量:primes[]存所有质数,cnt表示质数的个数,st[]表示当前的数有没有被筛过
int primes[N], cnt;
bool st[N];
//筛出1~n中所有的质数
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )   //i从2开始,因为最小的质数是2
    { 
        if (!st[i]) primes[cnt ++ ] = i;   //如果当前的数没有被筛过的话,说明他没有质因子,就是一个质数,把他存到质数数组里
                                           //cnt表示质数的个数,也就是用来控制数组下标。i就是要枚举的找的数
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) 
                break;
        }
    }
}

五、扩展欧几里得算法

1.裴蜀定理

若 a,b 是整数,且 (a,b)=d,那么对于任意的整数 x,y,ax+by 都一定是 d 的倍数,特别地,一定存在整数 x,y,使 ax+by=d 成立。

2.作用

扩展欧几里得算法可以在 O(logn) 的时间复杂度内求出系数 x,y。扩展欧几里德算法是用来解决裴蜀定理的,是在欧几里得算法求解最大公约数的过程中求出一组解x,y。

3.代码模板
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)
    {
        x = 1; y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x);
    y -= (a/b) * x;
    return d;
}

六、欧拉函数

1.内容:

在这里插入图片描述

2.性质:

在这里插入图片描述

3.作用:

下面的代码可以在 O(n)的时间复杂度内求出 1∼n 中所有数的欧拉函数:

4.代码模板:
int primes[N], euler[N], cnt;
bool st[N];

// 质数存在primes[]中,euler[i] 表示
// i的欧拉函数
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;
        }
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0)
            {
                euler[i * primes[j]] = euler[i] * primes[j];
                break;
            }
            euler[i * primes[j]] = euler[i] * (primes[j] - 1);
        }
    }
}

例题

一、等差数列

1.解题思路

在这里插入图片描述

2.代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010; //数据范围

int a[N];

int gcd(int a, int b)  //欧几里得算法
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    sort(a, a + n);  //给所有数排个序

    int d = 0;  //初始值为0,因为0和另一个数的最大公约数都是另一个数本身
    for (int i = 1; i < n; i ++ ) d = gcd(d, a[i] - a[0]);  //求公差:所有项与第一项的差的最大公约数

    if (!d) printf("%d\n", n);  //特判:如果d=0,所有数都一样,说明为常数序列
    else printf("%d\n", (a[n - 1] - a[0]) / d + 1);  //根据等差数列公式求出项数

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值