快速幂

数论基础知识点_gcd

数论基础知识点_快速幂_02,时间复杂度数论基础知识点_时间复杂度_03

LL qmi(int a, int b, int p)
{
    LL res = 1 % p;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}

应用:求逆元

假如数论基础知识点_gcd_04
称x为b的模n乘法逆元。

当n为质数时,可以用快速幂求逆元:

a / b ≡ a * x (mod n)
两边同乘b可得 a ≡ a * b * x (mod n)
即 1 ≡ b * x (mod n)
同 b * x ≡ 1 (mod n)
由费马小定理可知,当n为质数时
b ^ (n - 1) ≡ 1 (mod n)
拆一个b出来可得 b * b ^ (n - 2) ≡ 1 (mod n)
故当n为质数时,b的乘法逆元 x = b ^ (n - 2)

当n不是质数时,可以用扩展欧几里得算法求逆元:

a有逆元的充要条件是a与p互质,所以gcd(a, p) = 1
假设a的逆元为x,那么有a * x ≡ 1 (mod p)
等价:ax + py = 1
exgcd(a, p, x, y)

快速幂求逆元

LL qmi(int a, int b, int p)
{
    LL res = 1;
    while (b)
    {
        if (b & 1) res = res * a % p;
        a = a * (LL)a % p;
        b >>= 1;
    }
    return res;
}
int main()
{
    int n;
    scanf("%d", &n);
    while (n -- )
    {
        int a, p;
        scanf("%d%d", &a, &p);
        if (a % p == 0) puts("impossible");
        else printf("%lld\n", qmi(a, p - 2, p));
    }

    return 0;
}

扩展欧几里得算法求逆元

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;
}


int main()
{
    cin >> n;
    while (n --)
    {
        int a, p, x, y;
        // if (a < p) swap(a, p);
        cin >>  a >> p;
        int d = exgcd(a, p, x, y);
        if (d == 1) cout << ((LL)x + p) % p << endl;//保证x是正数
        else puts("impossible");

    }
    return 0;
}

矩阵快速幂

大家都知道 Fibonacci 数列吧,f1=1,f2=1,f3=2,f4=3,…,fn=fn−1+fn−2。

现在问题很简单,输入 n 和 m,求 fn 的前 n 项和 Sn mod m。

A = [[1, 1, 1],
[1, 0, 1],
[0, 0, 1]]

[fn,fn-1,Sn] = [fn-1,fn-2,Sn-1] A = [f2,f1,S2] A^(n-2)
将快速幂的元素改成矩阵即可

时间复杂度 O(logn)
注:该题矩阵快速幂的时间复杂度虽然是 O(logn),看似能跑的飞快,但实际上还有 27的常数,并不能跑的飞快,当然过这题毫无压力

const int N = 3;

int n, m;
// c = a * b
void mul(int c[], int a[], int b[][N])
{
    int temp[N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            temp[i] = (temp[i] + (LL)a[j] * b[j][i]) % m;
    // 用temp,不能用函数传参的指针
    memcpy(c, temp, sizeof temp);
}

void mul(int c[][N], int a[][N], int b[][N])
{
    int temp[N][N] = {0};
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            for (int k = 0; k < N; k ++ )
                temp[i][j] = (temp[i][j] + (LL)a[i][k] * b[k][j]) % m;

    memcpy(c, temp, sizeof temp);
}

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

    int f1[N] = {1, 1, 1};
    int a[N][N] = {
        {0, 1, 0},
        {1, 1, 1},
        {0, 0, 1}
    };

    n -- ;
    while (n)
    {
        if (n & 1) mul(f1, f1, a);  // res = res * a
        mul(a, a, a);  // a = a * a
        n >>= 1;
    }
    cout << f1[2] << endl;
    return 0;
}
IA = lambda:map(int, input().split())

n, MOD = IA()

a = [[1, 1, 1],
     [1, 0, 1],
     [0, 0, 1]]

f1 = [1, 1, 2]

def mul1(a, b):
    temp = [0 for i in range(len(a))]
    for i in range(len(a)):
        for j in range(len(b)):
            temp[i] = (temp[i] + a[j] * b[j][i]) % MOD
    return temp
def mul(a, b):
    temp = [[0 for j in range(len(a[0]))] for i in range(len(a))]
    for i in range(len(a)):
        for j in range(len(b)):
            for k in range(len(b[0])):
                temp[i][j] = (temp[i][j] + a[i][k] * b[k][j]) % MOD
    return temp
                
n -= 2
while n > 0:
    if (n & 1) == 1:
        f1 = mul1(f1, a)
    a = mul(a, a)
    n >>= 1

print(f1[2])

算术基本定理

算术基本定理
所有正整数都可以分解成质因子乘积的形式:
数论基础知识点_快速幂_05

则N的约数个数:数论基础知识点_算法_06
约数之和
数论基础知识点_ci_07

解释
假设N的一个约数为D
数论基础知识点_ci_08
其中bi可以取到0,范围是0<= bi <= a

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

约数的个数就是每个bi对应多少种选法相乘
即约数个数就为数论基础知识点_快速幂_09

约数之和
数论基础知识点_ci_07

解释
因为每一个约数为数论基础知识点_ci_08

那么数论基础知识点_快速幂_12
这个公式的意思就是从每个括号里面取出来一个数然后相乘,就能得到一个约数Di
然后所有的约数Di相加就得到约数之和S

举个例子
对于S = 42来说,42对应的结果里面有一个为20,20 = (1 + 2 + 4 + 5 + 10 + 20)
20 = 2^2*5
对于两个质因数2和5来说
2可以取0,1,2次,5可以取0,1次
所以S = (1+2+2^2)(1+5) = 42

应用
输入正整数 X,求 X 的大于 1 的因子组成的满足任意前一项都能整除后一项的严格递增序列的最大长度,以及满足最大长度的序列的个数。
给一个数S,让你找到有多少数能够满足它约数的和等于S。
题目

筛法求素数

线性筛

o(n) 时间内求出1~n中所有质数以及每个数的最小质因子
关键点:

  1. 筛掉的一定是合数,且用最小质因子筛的(primes[j] 一定不大于i的最小质因子)
  2. 合数是否一定会被筛掉
N = (1 << 20) + 10
primes = [0 for i in range(N)]
cnt = 0
st = [False for i in range(N)] # 当前的数有没有被筛过
minp = [0 for i in range(N)]

def get_primes(n):
    global cnt
    for i in range(2, n+1):
        if st[i] == False:
            primes[cnt] = i
            minp[i] = i
            cnt += 1
        j = 0
        while i * primes[j] <= n:
            st[i * primes[j]] = True
            minp [ i * primes[j] ] = primes[j]
            if i % primes[j] == 0:
                break
            j += 1
get_primes(N-1)      

while True:
    try:
        n = int(input())
        tot = 0
        down = 1
        up = 1
        while n > 1:
            p = minp[n]
            # print(n, p)
            i = 0
            while n % p == 0:
                i += 1
                down *= i
                tot += 1
                up *= tot
                n //= p           
        print(tot, up // down)
    except:
        break

最大公约数

辗转相除法

o(logn)
gcd(a,b) =gcd (b,a%b)

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

辗转相减法

时间复杂度:o(n)
gcd(a,b) = gcd(b,a-b)
主要应用
辗转相减法可以用来求若干个形如数论基础知识点_时间复杂度_13数的求数论基础知识点_gcd_14最大公约数d,分子分母分开求,但返回结果为$ {p}^{d}数论基础知识点_ci_15

算法推导
数论基础知识点_算法_16

int gcd_sub(int a, int b){
	if(a < b) swap(a, b);
	if(b == 1) return a; // 此时p^y = 1即y = 0
					     // 由于0和任何正整数r的最大公约数都是r,所以此时应返回p^r,即a
	return gcd_sub(b, a / b);
}

例题
AcWing 1246. 等差数列、AcWing 1223. 最大比例

扩展欧几里得算法

裴蜀定理:
指对于任意正整数对(a,b),如果gcd(a,b) = d,则一定存在非零整数x和y ,使得 ax+by = d
扩展欧几里得算法 可以求出一组系数x0,y0,d。

我们来分情况讨论一下扩展欧几里得定理:

  1. 当b = 0时,a和b的最大公约数为a.则x = 1;
  2. 当b ≠ 0时,
    by+(a mod b)x = gcd(a,b)
    ->by+(a - a/b * b)x = gcd(a,b)
    ->ax+b(y - a/b * x) = gcd(a,b)

    即当我们用扩展欧几里得定理求x和y时,欧几里得定理每递归一次x不用变,y->y-a/b * x即可
    如果我们求出x和y的一对,我们记为x0和y0
    那么其他的x和y可以通过x0,y0表示:
    令a=a/gcd(a,b) , b=b/gcd(a,b),任意意一组解:
    数论基础知识点_快速幂_17数论基础知识点_gcd_18
    证明
    ax0+by0 = gcd(a,b)
    ax+b*y = gcd(a,b)
    如果x0比x小,那么y0就应该比y大,这样加起来的结果才可能相同
    所以,a(x-x0) = b(y0-y);
    两边同时除以一个gcd(a,b)
    得到:a(x-x0) = b(y0-y)
    那么b就应该能整除a(x-x0),又因为a和b互质,
    所以b应该能整除(x-x0)
    所以(x-x0) = kb则 x = x0+kb
    y = y0-ka
    这里的k可正可负
IA = lambda : map(int, input().split())
T = int(input())
# 返回ax + by = gcd(a,b)方程的一组解x0,y0以及gcd(a,b)
def exgcd(a, b):
    if b == 0:
        return 1, 0, a
    x, y, d = exgcd(b, a % b)
    return y, x - a // b * y, d
for _ in range(T):
    n, d, x, y = IA()
    x0, y0, gcd = exgcd(n, d)
    if (y - x) % gcd !=0 :
        print("Impossible")
    else:
        n //= gcd
        y0 *= (y - x) // gcd
        print((y0 % n + n) % n)

例题

卡特兰数

定义

首先看一下卡特兰数。若一个数列h数论基础知识点_快速幂_19满足:
数论基础知识点_算法_20
则称h数论基础知识点_快速幂_19为卡特兰数列。
还有一种形式若:
数论基础知识点_快速幂_22

应用

本质:任何时间以前,某种操作的个数都不少于另一种操作的个数的方法数

https://www.acwing.com/problem/content/891/

给定n个0和n个1,它们将按照某种顺序排成长度为2n的序列,求它们能排列成的所有序列中,能够满足任意前缀序列中0的个数都不少于1的个数的序列有多少个

https://www.acwing.com/problem/content/417/

栈的出栈顺序的个数也是上述问题,可以将0看成进栈和1看成出栈。

1push 2push 3push 3pop 2pop 1pop 序列为321
1push 1pop 2push 2pop 3push 3pop 序列为123
1push 2push 2pop 1pop 3push 3pop 序列为213
1push 1pop 2push 3push 3pop 2pop 序列为132
1push 2push 2pop 3push 3pop 1pop 序列为231

证明

0代表向右走,1代表向上走,在坐标轴上转化为路径问题,合法路径不能超过y=x。

数论基础知识点_gcd_23

思想:任何一条非法路径对应一条从(0,0)到(n-1,n+1)的路径

图中橙色与紫色满足条件.
那满足条件的路线一共有多少呢?直接求不是很好求,所以我们可以转化一下,用所有的路线减去不满足条
件的路线,那不满足条件的路线有什么样的特点呢?
这样的路线上必然存在一点的纵坐标是大于横坐标的,即这样的路线一定经过了y=x+1这条线,以图中
的褐色为例.我们将褐色的路线第一次经过y=x+1以后的路线关于y=x+1做轴对称,用黑色的线表
示.我们可以发现所有不满足的条件的路线都可以通过这种方式将终点变为(n-1,n+1),在这个例子中
即为数论基础知识点_时间复杂度_24所有的路线为C数论基础知识点_快速幂_25数论基础知识点_算法_26,因为一共有12个空,要放6个0所以为C数论基础知识点_快速幂_25数论基础知识点_算法_26,同理:不满条件的路线为
C数论基础知识点_快速幂_25数论基础知识点_算法_26.因此在本题中满足条件的路线为:数论基础知识点_快速幂_31(所有的路线)-数论基础知识点_ci_32(不满足条件的路线)
所以在一般问题中即为:
数论基础知识点_ci_33

数论基础知识点_gcd_34