「外出学习」数论学习笔记

取模#

(1)5÷3=12a=bc+d(2)a÷b=cdb>d0(3)a,b,c=a/b,d=amodb(4)(a+b)modc=[amodc+bmodc]modca=xc+a,b=yc+b(xc+a+yc+b)modc=(a+b)modc

模运算#

(a+b)modc=(amodc+bmodc)modc(ab)modc=(amodcbmodc)modc(ab)modc=[(amodc)(bmodc)]modc

#include <iostream>
using namespace std;

int a, b, c;

int main() {
    cin >> a >> b >> c;
    cout << (a + b) % c << endl;
    cout << ((a - b) % c + c) % c << endl;
    cout << ((long long)a * b) % c << endl;
    cout << (1ll * a * b) % c << endl;
    return 0;
}

读入两个数 n,p,现在求 (n!)modp 是多少?(2<p109,1n1000)

#include <iostream>
using namespace std;

int n, p;

int main() {
    cin >> n >> p;
    int ans = 1;
    for (int i = 1; i <= n; ++ i) {
        ans = 1ll * ans * i % p;
    }
    cout << ans << endl;
    return 0;
}

n!modp=[(n1)!modp×nmodp]modp

gcd、lcm#

gcd:最大公因数。

lcm:最小公倍数。

有两个数 a,b,求 gcd(a,b)

gcd(a,b)=gg|ag|ba=ag,b=bggcd(a,b)=1,abgcd(a,b)=gcd(ab,b)


g=gcd(a,b)=gcd(ab,b)a=ag,b=bggcd(a,b)=1gcd(ab,b)=gcd[(ab)g,bg]=gcd(ab,b)ggcd(a,b)=1gcd(ab,b)=1gcd(ab,b)=ggcd(a,b)=gcd(ab,b)=gcd(abb,b)=gcd(abbb,b)=gcd(a,b)=gcd(amodb,b)

// gcd(a, 0) = a
int gcd(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}

证明以上代码是 log 复杂度。

gcd(a,b)gcd(b,amodb)(ab)amodb<12a


amodb<12d(1)b12aamodb<b12a(2)b12aab1=ab<12a

每次都砍一半,所以这是一个 log 复杂度的算法。

n 个数,a1,a2,a3,,an,求这 n 个数的 gcd

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

const int N = 1e5 + 5;

int n;
int a[N];

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

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++ i) {
        cin >> a[i];
    }
    int ans = a[1];
    for (int i = 2; i <= n; ++ i) {
        ans = gcd(ans, a[i]);
    }
    cout << ans << endl;
    return 0;
}

复杂度:On+loga,因为 ans 只能越来越小。

gcd(a,b)=g,lcm(a,b)=lab=gll=abg

lcm(a, b) = a / gcd(a, b) * b;

计算 xymodp(x,y,p109)

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

int x, y, p;

int qpow(int x, int y, int p) {
    if (y == 0)    return 1;
    int z = qpow(x, y / 2, p);
    z = 1ll * z * z % p;
    if (y & 1) {
        z = 1ll * z * x % p;
    }
    return z;
}

int main() {
    cin >> x >> y >> p;
    cout << qpow(x, y, p);
    return 0;
}

给你三个数 x,y,p,计算 xymodp(x,y,p1018),同时不支持 __int128

x37=x+x+x++xx37=x18+x18+xx18=x9+x9

将乘法转化为加法。

int qtimes(int x, int y, int p) {
    if (y == 0)    return 0;
    int z = qtimes(x, y / 2, p);
    z = (z + z) % p;
    if (y & 1) {
        z = (1ll * z + x) % p;
    }
    return z;
}

矩阵#

nm 列的二维数组就是一个 nm 列的矩阵。

矩阵加法#

把对应位置上的数加起来就行了。

[123456]+[233233]=[356689]

矩阵减法#

对应位置上的数相减。

[123456][233233]=[110223]

数乘矩阵#

2[123456]=[24681012]

矩阵乘法#

矩阵 A 能与矩阵 B 做乘法,当且仅当 Anm 列,Bmk 列。

(只有当第一个矩阵的列数等于第二个矩阵的行数是才能进行矩阵乘法,结果矩阵的行是第一个矩阵的行,列是第二个矩阵的列数)

[1234]×[123233]=[589111821]

struct matrix {
    int n, m;
    int z[10][10];
    matrix() { // 构造函数
        n = m = 0;
        memset(z, 0, sizeof(z));
    }
};
matrix m1, m2, m3;

matrix operator * (const matrix &m1, const matrix &m2) { // 重载运算符
    matrix m;
    m.n = m1.n, m.m = m2.m;
    for (int i = 1; i <= m.n; ++ i) {
        for (int j = 1; j <= m.m; ++ j) {
            for (int k = 1; k <= m1.m; ++ k) {
                m.z[i][j] += m1.z[i][k] * m2.z[k][j];
            }
        }
    }
    return m;
}

m3 = m1 * m2;

memset#

memset(z, 0, sizeof z);
memset(z, -1, sizeof z);
memset(z, 0x3f, sizeof z); // 赋成 0x3f3f3f3f

给两个数 n,p,求斐波那契而数列第 nfnmodp 是多少。(n,p)109

暴力

cin >> n >> p;
int a = 0, b = 1;
for (int i = 2; i <= n; ++ i) {
    int c = a + b;
    if (c >= p)    c -= p;
    a = b, b = c;
}

[fi1fi1]×[1110]=[fifi1][fifi1]×[1110]=[fi+1fi][f1f0]×[1110]n=[fn+1,fn]

matrix qpow(matrix x, int y) {
    if (y == 1)    return x;
    matrix z = qpow(x, y / 2);
    z = z * z;
    if (y & 1)    z = z * x;
    return z;
}

int main() {
    cin >> n >> p;
    matrix A, B;
    A.n = 1, A.m = 2;
    A.z[1][1] = 1, A.z[1][2] = 0;
    B.n = 2, B.m = 2;
    B.z[1][1] = 1, B.z[1][2] = 1;
    B.z[2][1] = 1, B.z[2][2] = 0;
    matrix C = A * qpow(B, n);
    int ans = C.z[1][2];
    cout << ans % p << '\n';
    return 0;
}

矩阵乘法没有交换律,有结合律。

n 个点,走 k 步,走到 n 号点的方案数。(n100,k109)

f(i,j) 为走了 i 步,走到了 j 的方案数。

f(0,1)=1,f(0,2)=f(0,3)==f(0,n)=0

f(i,j)=f(i1,p1)+f(i1,p2)+f(i1,p3)+

cin >> n;
for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
        cin >> z[i][j];
    }
}
f[0][1][1] = 1;
for (int a = 1; a <= k; ++ a) { // 走了a步
    for (int d = 1;d <= 1; ++ d) { // 凑矩阵
        for (int b = 1; b <= n; ++ b) { // 走到了b
            for (int c = 1; c <= n; ++ c) { // 第a-1步在c
                f[a][d][b] += f[a - 1][d][c] * z[c][b];
            }
        }
    }
}
cout << f[k][1][n] << '\n';
return 0;

我们可以把 f[a], f[a-1], z 看成矩阵。

fa=fz1×z

ak=f0×zk

P4159#

拆点,一条长度为 5 的边拆成五条长度为 1 的边。

cin >> n;
for (int i = 1; i <= n; ++ i) {
    z[i][n + (i - 1) * 9 + 1] = 1;
    for (int j = 1; j < 9; ++ j) {
        z[n + (i - 1) * 9 + j][n + (i - 1) * 9 + j + 1] = 1;
    }
}

初等数论#

质数(素数)#

1 之外,只有 1 和它本身作为因子的数就是质数。

所有正整数可分为 1、素数和合数。


判断素数#

bool is_prime(int x) { // O(x)
    if (x < 2)    return false;
    for (int i = 2; i < x; ++ i) {
        if (x % i == 0)    return false;
    }
    return true;
}
bool is_prime(int x) { // O(sqrt(x))
    if (x < 2)    return false;
    int y = sqrt(x) + 0.5;
    for (int i = 2; i <= sqrt(x); ++ i) {
        if (x % i == 0)    return false;
    }
    return true;
}
bool is_prime(int x) { // O(sqrt(x))
    if (x < 2)    return false;
    for (int i = 2; i * i <= x; ++ i) {
        if (x % i == 0)    return false;
    }
    return true;
}

分解质因数#

void factorize(int x) { // 对 x 分解质因数
    for (int a = 2; a * a <= x; ++ a) {
        if (x % a == 0) {
            ++ cnt;
            prime[cnt] = a;
            while (x % a == 0) {
                ++ num[cnt];
                x /= a;
            }
        }
    }
    if (x != 1) {
        ++ cnt;
        prime[cnt] = x;
        num[cnt] = 1;
    }
}

CF45G#

1,2,3,4,5,n,把这些数分成最少的组,使得每一组的和都是质数。(n2000)

对于任何一个大于等于 4 的偶数,它一定可以分成 2 个质数和,任何一个大于等于 7 的奇数,它一定可以拆成 3 个质数之和。

算出 1+2+3+4++n=x

如果 x 是偶数,那么这时答案是 2x 是奇数,答案小于等于 3

n=2 时,答案为 1

如果 x2 是质数,那么答案是 2

否则,答案是 3

逆元#

费马小定理:当 p 是质数 (1a<p)apa(modp)ap1modp=1

ap11(modp)ap21a(modp)

费马小定理使用条件:

  1. p 是质数

  2. gcd(a,p)=1

p 不为质数,但 gcd(a,p)=1,则使用欧拉定理。

欧拉定理:aφ(p)1(modp)

φ(p)1p 中有多少个数和 p 互质。

aφ(p)11a(modp)

p 是质数时,φ(p)=p1

gcd(a,p)1 时,可以认为不存在逆元。

φ(n) 怎么算?

  1. 枚举 1n,判断是否互质。Onlogn

  2. n 是一个质数,φ(n)=n1

  3. n=p2φ(n)=p2p=p(p1)

  4. n=pkφ(n)=p1pn

n=pk,φ(n)=p1pnn=p1p2,φ(n)=nnp1np2+np1p2φ(n)=nnp1np2+np1p2=n(11p1)(11p2)

n=p1k1p2k2ptktφ(n)=n(11p1)(11p2)(11pt)

int get_phi(int n) {
    int phi = n;
    for (int i = 2; i * i <= n; ++ i) {
        if (n % i == 0) {
            phi = phi / i * (i - 1);
            while (n % i == 0) {
                n = n / i;
            }
        }
    }
    if (n != 1) {
        phi = phi / n * (n - 1);
    }
    return phi;
}
cin >> n >> p;
// fac[i] = i!
fac[0] = 1;
for (int i = 1; i <= n; ++ i) {
    fac[i] = 1ll * fac[i - 1] * i % p;
}
// ifac[i] = 1 / i! i!的逆元
ifac[n] = ksm(fac[n], p - 2, p);
for (int i = n - 1; i >= 0; i --) {
    ifac[i] = 1ll * ifac[i + 1] * (i + 1) % p;
    // 1/i! = 1/(i + 1)! * (i + 1)
}
// inv[i] = 1 / i i的逆元
// 1 / i = (i - 1)! / i!
for (int i = 1; i <= n; ++ i) {
    inv[i] = 1ll * fac[i - 1] * ifac[i] % p;
}

已知 1n1 的逆元,求 n 的逆元。

p=ki+r1r<i0ki+r(modp)rki(modp)rik(modp)1i=kr(modp)1ikinvr

cin >> n >> p;
inv[1] = 1;
for (int i = 2; i <= n; ++ i) {
    int k = p / i;
    int r = p % i;
    // p = k * i + r
    // 0 = k * i + r (mod p)
    // -r = k * i(mod p)
    // 1 / i = -k / r(mod p)
    inv[i] = 1ll * (p - k) * inv[r] % p;
}

米勒拉宾素性测试#

给定 n,如何判断 n 是不是质数。

如果 n 是质数,n1=d×2r

1a<n

  1. admodn=1

  2. 存在 0i<rad×2imodn=n1

n 是质数,则这两条性质至少有一条成立,但是不是质数时这两个性质也可能成立。

如果两个性质都不成立,则一定不是质数。

// n - 1 = d * 2^r
// 找一个 1 <= a < n
// 1. a^d % n = 1
// 2. 存在一个 0 <= i < r,使得 a ^ (d * 2) % n = n - 1
// 当 n 是质数时,两个性质至少一个成立


bool miller_rabin(int n, int a) { // 检查n,a能否满足两个性质中的任意一个
    // log(n)
    int d = n - 1, r = 0;
    while (d % 2 == 0) {
        d = d / 2, ++ r;
    }
    int x = qpow(a, d, n);
    if (x == 1)    return true; // 性质一
    for (int i = 0; i < r; ++ i) { // 性质二
        if (x == n - 1)    return true;
        x = 1ll * x * x % n;
    }
    return false;
}


bool is_prime1(int n) {
    if (n < 2)    return false;
    for (int i = 1; i <= 23; ++ i) {
        int a = rand() % (n - 1) + 1;
        if (!miller_rabin(n, a))    return false;
    }
    return true;
}

int prime_list[] = {2, 3, 5, 7, 13, 23, 37, 73};

bool is_prime2(int n) {
    if (n < 2)    return false;
    for (int i = 0; i < 8; ++ i) {
        if (n == prime_list[i])    return true;
        if (n % prime_list[i] == 0)    return false;
        if (!miller_rabin(n, prime_list[i] % n))    return false;
    }
    return true;
}

不定方程#

解方程 xa+yb=g(gcd(a,b)=g)

gcd(a,b)gcd(b,amodb)xb+y(amodb)=g

xb+y(amodb)=gxa+yb=gxb+y(abab)=gxb+yaybab=gya+(xyab)b=gx=y,y=(xyab)

int exgcd(int a, int b, int &x, int &y) {
    // 求 g = gcd(a, b)
    // 以及 xa + yb = g
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    int xp, yp;
    int g = exgcd(b, a % b, xp, yp);
    // xp * b + yp * (a % b) = g
    // xp * b + yp * (a - b * (a  /b)) = g
    // xp * b + yp * a - yp * b * (a / b) = g
    // yp * a + (xp - yp * (a / b)) * b = g
    x = yp, y = xp - yp * (a / b);
    return g;
}

xa+ybx,y 是整数,求 x,y 能够表示出来的最小正整数。

答案:gcd(a,b)

裴属定理。

xa+yb=z,只有当 zmodgcd(a,b)=0 时才有解。

同余方程#

ax1(modb)ax=yb+1axyb=1

ax+by=ga(x0+n)+b(y0m)=gan=bmnb=ma{x=x0+bgky=y0agk

2x+3y=1{x=2y=1x2+ny1m2(2+n)+3(1m)=12n=3mn3=m2{x=2+3ky=12k

中国剩余定理#

{xmodp1=a1xmodp2=a2xmodpn=an

{xmodp1=a1x=k1p1+a1xmodp2=a2x=k2p2+a2xmodpn=an

{xmod3=1xmod5=1

最小的 x1

(1+k)mod3=1kmod3=0(1+k)mod5=1kmod5=0x=1+15kxmod15=1

{xmodp1=a1xmodp2=a2x=a1+p1

void solve(int p1, int a1, int p2, int a2, int &p, int &a) { // 暴力
    // x % p1 = a1
    // x % p2 = a2
    // x % p = a
    // 时间复杂度: O(min(p1, p2))
    if (p1 < p2)    swap(p1, p2), swap(a1, a2);
    int x = a1;
    int g = gcd(p1, p2);
    int l = p1 / g * p2;
    while (x % p2 != a2 && x <= l) {
        x += p1;
    }
    if (x > l)    p = -1, a = -1;
    else    p = l, a = x;
}

{xmodp1=a1xmodp2=a2x=k1p1+a1=k2p2+a2k1p1k2p2=a2a1xa+yb=g

void solve(int p1, int a1, int p2, int a2, int &p, int &a) {
// 不要求 p1, p2 互质
// x % p1 = a1
// x % p2 = a2
// x % p = a
// x = k1 * p1 + a1 = k2 * p2 + a2
// k1 * p1 - k2 * p2 = a2 - a1
    int g, k1, k2;
    g = exgcd(p1, p2, k1, k2);
// k1 * p1 + k2 * p2 = g
    k2 = -k2;
// k1 * p1 - k2 * p2 = g
    if ((a2 - a1) % g != 0) {
        p = -1, a = -1;
    }
    else {
        int k = (a2 - a1) / g;
        k1 = k1 * k, k2 = k2 * k;
        // k1 * p1 - k2 * p2 = a2 - a1
        int x = k1 * p1 + a1;
        p = p1 / g * p2;
        a = (x % p + p) % p;
    }
}

筛法#

给定一个整数 n,希望在尽量短的时间内把 [1,n] 的质数找出来。

  1. Onn

  2. Miller-Rabin Onlogn

  3. On2+n3++nnn(11+12++1n)nlogn

for (int a = 2; a <= n; ++ a) {
    for (int b = a + a; b <= n; b += a) {
        not_prime[b] = true;
    }
}
for (int a = 2; a <= n; ++ a) {
    if (not_prime[a] == false) {
        prime[++ cnt] = a;
    }
}

优化 Onloglogn

for (int a = 2; a <= n; ++ a) {
    if (not_prime[a] == false) {
        for (int b = a + a; b <= n; b += a) {
            not_prime[b] = true;
        }
    }
}

线性筛#

让每个数被它最小的质因子筛掉。On

for (int i = 2; i <= n; ++ i) {
    if (not_prime[i] == false) {
        prime[++ cnt] = i;
    }
    for (int j = 1; j <= cnt; ++ j) {
        int x = prime[j] * i; // 筛掉第 j 个质数的 i 倍 
        if (x > n)    break;
        not_prime[x] = true;
        if (i % prime[j] == 0)    break;
        // y = prime[j + 1] * i
        // i = prime[j] * k
        // y = prime[j + 1] * prime[j] * k
    }
}

106 bool 1M

106 int 4M

P3383#

欧拉筛求积性函数的值#

积性函数:当 gcd(a,b)=1,f(a)f(b)=f(ab)

完全积性函数:f(a)f(b)=f(ab)

n=p1k1p2k2ptktφ(n)=n(11p1)(11p2)(11pt)m=q1r1q2r2qwrwφ(m)=m(11q1)(11q2)(11qw)mn=p1k1p2k2ptktq1r1qwrwφ(nm)=nm(11p1)(11p2)(11pt)(11q1)(11q2(11qw)φ(nm)=φ(n)φ(m)

phi[1] = 1;
for (int i = 2; i <= n; ++ i) {
    if (not_prime[i] == false) {
        prime[++ cnt] = i;
        phi[i] = i - 1;
    }
    for (int j = 1; j <= cnt; ++ j) {
        int x = prime[j] * i; // 筛掉第 j 个质数的 i 倍 
        if (x > n)    break;
        not_prime[x] = true;
        if (i % prime[j] == 0) {
            phi[x] = prime[j] * phi[i];
            break;
        }
    }
}

φ(n)=n(11p1)(11pk)φ(i)=i(11q1)(11qr)ipj=q1q2qktk+1qrφ(ipj)=pji(11q1)(11qr)φ(ipj)=pjφ(i)

BSGS 算法#

给定三个数 a,b,pp 是质数,解方程 axmodp=b(a,b,p109)

暴力

int solve(int a, int b, int p) {
// O(p)
    int v = 1;
    for (int x = 0; x <= p - 2; ++ x) {
        if (v == b)    return x;
        v = 1ll * v * a % p;
    }
    return -1;
}

为什么一定会在 1 处循环呢?

因为 ap1modp=1

ap1+kmodp=ap1akmodp=akmodp

对于该方程,要枚举 p1 个数,那我们将这 p1 个数分组,s 为每组的大小

a0a1a2as1(as)(÷as)asas+1as+2a2s1a2sa2s+1a2s+2a3s1

若第 2 组数中出现了 b,那么在第 1 组中,一定出现了 bas

#include <set>
using namespace std;

int solve(int a, int b, int p) {
    int s = sqrt(p);
    int v = 1;
    set<int> se;
    for (int i = 0; i < s; ++ i) {
        se.insert(v);
        v = 1ll * v * a % p;
    }
    // O(p / s)
    for (int i = 0; i * s <= p; ++ i) { // 看答案是否在第 i 行里面
        // 要看 b * a (-is) 是否在第 0 行出现
        int c = 1ll * b * qpow(qpow(a, i * s, p), p - 2, p) % p;
        if (se.count(c) != 0) {
            int v = qpow(a, i * s, p); // 第 i 行的第一个数
            for (int j = i * s; ; ++ j) { // O(s)
                if (v == b)    return j;
                v = 1ll * v * a % p;
            }
        }
    }
    return -1;
}

O(ps+s)=O(max(ps,s))

若取 s=p,则为 Op

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/17398926.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示