Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

    • 一些小问题
    • 二次剩余学习笔记
    • 卡特兰数学习笔记
    • 同余最短路学习笔记
    • 康托展开学习笔记
    • 快速傅里叶变换学习笔记
    • 拉格朗日插值学习笔记
    • 拓展欧几里得学习笔记
    • 斐波那契数列学习笔记
    • 斯特林数笔记
    • 本原勾股数组学习笔记
    • 模运算学习笔记
    • 欧拉函数
    • 类欧几里得学习笔记
    • 素性检测与大数分解
      • 前置1. 费马小定理
      • 前置2. 二次探测
      • 算法思想
      • 算法实现
      • 正确性
      • 模板
    • 素数计数问题
    • 组合数问题
    • 高斯整数学习笔记
  • 动态规划

  • 暴力

  • 图论

  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

素性检测与大数分解

# 素性检测

# 前置1. 费马小定理

ap−1≡1modp其中p为质数a^{p-1}\equiv 1 \mod{p}\quad \text{其中p为质数} ap−1≡1modp其中p为质数

简要证明:

对于数列{1,2,3,...,p−1}\{1,2,3,...,p-1\}{1,2,3,...,p−1},对每个数字计算f(x)=xamodpf(x)=xa\mod pf(x)=xamodp,其中aaa为满足gcd(a,p)=1gcd(a,p)=1gcd(a,p)=1的任意整数,得到新的数列{1a,2a,3a,…,(p−1)a}\{1a,2a,3a,\dots,(p-1)a\}{1a,2a,3a,…,(p−1)a}

新的数列有一下性质

  1. 0<x<p0<x<p0<x<p:显然

  2. 数列中的数字两两不相同:

    假设原数列中有两个不同数字c,dc, dc,d,使得f(c)=f(d)f(c)=f(d)f(c)=f(d),即有(ac−ad)≡0modp(ac-ad)\equiv 0 \mod p(ac−ad)≡0modp

    即a(c−d)≡0modpa(c-d)\equiv 0 \mod pa(c−d)≡0modp,又因为(a,p)=1(a,p)=1(a,p)=1,因此(c−d)∣p(c-d)\mid p(c−d)∣p

    因此c−d=0c-d=0c−d=0,c=dc=dc=d,矛盾

根据上述性质,可以知道新的数列经过调整位置后可以得到原数列。

因此1×2×⋯×(p−1)≡1a×2a×⋯×(p−1)amodp1\times2\times\dots\times(p-1)\equiv 1a\times 2a\times \dots\times (p-1)a \mod p1×2×⋯×(p−1)≡1a×2a×⋯×(p−1)amodp

即(p−1)!≡ap−1(p−1)!modp(p-1)!\equiv a^{p-1} \ (p-1)! \mod p(p−1)!≡ap−1(p−1)!modp,又因为(p−1)!(p-1)!(p−1)!与ppp互质

因此ap−1≡1modpa^{p-1}\equiv 1 \mod pap−1≡1modp

# 前置2. 二次探测

若p为素数,则a2≡1modp的解为a≡1modp\text{若p为素数,则} a^2\equiv 1 \mod p\quad\text{的解为}\quad a \equiv 1\mod p 若p为素数,则a2≡1modp的解为a≡1modp

简要证明:

\begin{eqnarray}a^2-1 && \equiv 0 \mod p \\(a-1)(a+1) &&\equiv 0 \mod p \\\end{eqnarray}

由于ppp是素数,显然有(a−1)(a+1)≠np,n∈Z+(a-1)(a+1)\ne np, n\in Z^+(a−1)(a+1)=np,n∈Z+,因此

a−1≡0modp或a+1≡0modpa-1\equiv 0 \mod p \quad \text{或} \quad a+1\equiv 0 \mod p a−1≡0modp或a+1≡0modp

# 算法思想

设需要判断的数为ppp:

  1. 将p−1p-1p−1分解为2k∗t2^k*t2k∗t的形式
  2. 若ppp为素数,则定有a2k∗t≡1modpa^{2^k*t}\equiv 1\mod pa2k∗t≡1modp(对于任意满足(a,p)=1(a,p)=1(a,p)=1的整数)
  3. 因而a2k−1∗t≡1a^{2^{k-1}*t}\equiv 1a2k−1∗t≡1或−1-1−1(下面省略符号"modp\mod pmodp")
  4. 若上一步同余于111,则a2k−2∗t≡1a^{2^{k-2}*t}\equiv1a2k−2∗t≡1或a2k−2∗t≡−1a^{2^{k-2}*t}\equiv -1a2k−2∗t≡−1
  5. ......

多次随机选取不同的aaa,若任意条件不满足,则数ppp不是质数;否则,ppp极大概率是素数

# 算法实现

// 求余快速幂
long long qpow(long long base, long long t, long long mod) {
    long long res = 1;
    while (t) {
        if (t & 1) res = res * base % mod;
        t >>= 1;
        base = base * base % mod;
    }
    return res;
}

bool Miller_Rabin(long long p) {
    const int TEST_TIME = 8; // 测试8次

    if (p < 2) return false;
    if ((p & 1) == 0) return p == 2;

    long long t = p - 1;
    int k = 0;
    while ((t & 1) == 0) k++, t >>= 1;

    for (int i = 0; i < TEST_TIME; i++) {
        long long a = rand() % (p - 1) + 1; // 通常a可以直接取质数,如:2,5,7,11,...
        long long pow = qpow(a, t, p), last = pow;
        for (int j = 0; j < k; j++) {
            pow = pow * pow % p;
            if (pow == 1 && last != 1 && last != p - 1) return false; // 合数
            last = pow;
        }
        if (pow != 1) return false;
    }
    return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 正确性

The error made by the primality test is measured by the probability for a composite number to be declared probably prime. The more bases a are tried, the better the accuracy of the test. It can be shown that if n is composite, then at most 1⁄4 of the bases a are strong liars for n.

能力有限,实在看不懂这篇文章 (opens new window)

# 大数分解

先贴出算法的大致流程

from random import randint
from math import gcd

def Pollard_Rho(n: int):
    i, k = 0, 1
    x = randint(0, n - 1)
    y = x
    while True:
        i += 1
        x = (x * x - 1 + n) % n
        d = gcd(abs(y - x), n)
        if d != 1 and d != n:
            return d
        if i == k:
            k <<= 1
            y = x
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

下面来分析一下这个算法:

首先,对于无限序列[x][x][x],xix_ixi​仅由xi−1x_{i-1}xi−1​确定(即xi=(xi−12+1)modnx_i=(x_{i-1}^2+1) \mod nxi​=(xi−12​+1)modn,简记为xi=fn(xi−1)x_i=f_n(x_i-1)xi​=fn​(xi​−1)),由于ZnZ_nZn​是有限的,该序列一定会产生自身重复(回路),如下图(a)所示。由生日悖论可得,出现重复之前,预期执行步数为O(n)O(\sqrt{n})O(n​)。

假设nnn是一个合数,设nnn的某一个非平凡因子为ppp

对于序列<x′><x'><x′>,其中xi′=ximodpx'_i=x_i\mod pxi′​=xi​modp,则该序列满足xi′=fp(xi−1′)x'_i=f_p(x'_{i-1})xi′​=fp​(xi−1′​)(证明略)

因此序列<x′><x'><x′>进入循环前预期执行步数为O(p)O(\sqrt{p})O(p​)

令ttt为<x′><x'><x′>中第一个出现重复的值的下标,uuu表示循环长度,则xt+i′=xt+i+u′x'_{t+i}=x'_{t+i+u}xt+i′​=xt+i+u′​,若xt+i≠xt+i+ux_{t+i}\ne x_{t+i+u}xt+i​=xt+i+u​,则n∣(xt+i−xt+i+u)n\mid(x_{t+i}-x_{t+i+u})n∣(xt+i​−xt+i+u​),此时便找到了nnn的一个非平凡因子。

因而,当执行循环直至 kkk(见代码) 大于uuu时,在不改变yyy的前提下,沿着环走一圈,便能遇到xi≡ymodpx_i\equiv y\mod pxi​≡ymodp,此时便发现了nnn的一个因子。

参考:《算法导论》第三版

Ps. 显然,序列<x><x><x>对应的uuu与<x′><x'><x′>对应的u′u'u′满足u≥u′u\ge u'u≥u′


可以发现,每一步都得执行一次gcd,其复杂度为O(logn)O(log n)O(logn),而这部分是可以被优化掉的。

gcd(a,b)=gcd(acmodb,b)gcd(a,b)=gcd(ac \mod b, b) gcd(a,b)=gcd(acmodb,b)

因此,我们可以一段长度内的(y−xi)(y-x_i)(y−xi​)乘起来,再一起计算gcd

# 模板

ll Pollard_Rho(ll x){
    ll a = 0, c = rng() % (x - 1) + 1, b = c, mul = 1, tmp;

    int max_step = 1, step = 0;
    while(true){
        b = f(b, c, x);
        mul = mul_mod(mul, abs(a - b), x);
        if(mul == 0) return x;
        step++;
        if(step == max_step){
            tmp = __gcd(mul, x);
            if(tmp != 1) return tmp;
            max_step <<= 1;
            a = b;
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 例题 洛谷P4781

对于每个数字检验是否是质数,是质数就输出Prime;如果不是质数,输出它最大的质因子是哪个。

AC代码如下

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define int128 __int128_t
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;

mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

ll mul_mod(ll a, ll b, ll mod){ // 用一般的龟速乘会溢出
    return (a*b-(ll)((long double)a/mod*b)*mod+mod)%mod;
}

ll qpow(ll base, ll t, ll mod) {
    ll res = 1;
    while (t) {
        if (t & 1) res = mul_mod(base, res, mod);
        t >>= 1;
        base = mul_mod(base, base, mod);
    }
    return res;
}

bool Miller_Rabin(ll p) {
    const int TEST_TIME = 8; // 测试8次

    if (p < 2) return false;
    if ((p & 1) == 0) return p == 2;

    ll t = p - 1, k = 0;
    while ((t & 1) == 0) k++, t >>= 1;

    for (int i = 0; i < TEST_TIME; i++) {
        ll a = rng() % (p - 1) + 1; // 通常a可以直接取质数
        ll pow = qpow(a, t, p), last = pow;
        for (int j = 0; j < k; j++) {
            pow = mul_mod(pow, pow, p);
            if (pow == 1 && last != 1 && last != p - 1) return false; // 合数
            last = pow;
        }
        if (pow != 1) return false;
    }
    return true;
}

inline ll f(ll val, ll c, ll mod){
    return (mul_mod(val, val, mod) + c) % mod;
}

ll Pollard_Rho(ll x){
    ll a = 0, c = rng() % (x - 1) + 1, b = c, mul = 1, tmp;

    int max_step = 1, step = 0;
    while(true){
        b = f(b, c, x);
        mul = mul_mod(mul, abs(a - b), x);
        if(mul == 0) return x;
        step++;
        if(step == max_step){
            tmp = __gcd(mul, x);
            if(tmp != 1) return tmp;
            max_step <<= 1;
            a = b;
        }
    }
}

void get_max(ll x, ll & ans){
    if(x <= ans || x <= 1) return;
    if(Miller_Rabin(x)){
        ans = max(x, ans);
        return;
    }
    ll p = x;
    while(p >= x) p = Pollard_Rho(x);
    while(x % p == 0) x /= p;
    get_max(p, ans);
    get_max(x, ans);
}

void solve() {
    long long x;
    ll ans = 0;
    cin >> x;
    if(Miller_Rabin(x)){
        cout << "Prime\n";
    }else{
        get_max(x, ans);
        cout << (long long) ans << endl;
    }
}

signed main() {
    Android;
    int t;
    cin >> t;
    while (t--) solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
上次更新: 2021/02/24, 03:37:30
类欧几里得学习笔记
素数计数问题

← 类欧几里得学习笔记 素数计数问题→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式