2 条题解

  • 2
    @ 2026-8-12 15:26:51

    Miller-Rabin 的推导本质上是把费马小定理二次探测定理串联起来,形成一个可高效验证的判定链。

    费马小定理告诉我们:若 pp 是素数,gcd(a,p)=1\gcd(a,p)=1,则

    ap11(modp)a^{p-1} \equiv 1 \pmod p

    我们想用它的逆否命题来判合数:若 an1≢1(modn)a^{n-1} \not\equiv 1 \pmod n,则 nn 是合数。

    问题:存在合数(Carmichael 数)使得 an11(modn)a^{n-1} \equiv 1 \pmod n 对某些 aa 成立。仅靠费马小定理会漏判。

    pp 是奇素数,且 x21(modp)x^2 \equiv 1 \pmod p,则

    $$x \equiv 1 \pmod p \quad \text{或} \quad x \equiv -1 \pmod p$$

    逆否命题:若在模 nn 下算出 x21x^2 \equiv 1,但 x≢±1(modn)x \not\equiv \pm 1 \pmod n,则 nn 一定是合数

    为了把二次探测嵌入费马测试,我们将 n1n-1 分解为:

    n1=2sd(d 为奇数)n - 1 = 2^s \cdot d \quad (d \text{ 为奇数})

    这样 an1a^{n-1} 可以写成一个逐步平方的链

    $$a^{n-1} = a^{2^s \cdot d} = \underbrace{(\cdots((a^d)^2)^2\cdots)^2}_{s \text{ 次平方}}$$

    定义序列:

    $$t_0 = a^d, \quad t_1 = t_0^2, \quad t_2 = t_1^2, \quad \dots, \quad t_s = t_{s-1}^2 = a^{n-1}$$
    • 若满足 t0=ad±1(mod n)t_0 = a^d \equiv \pm 1 (\bmod \ n)。此时费马/二次探测直接判定。

    • 若满足 t0≢±1t_0 \not\equiv \pm 1。但我们知道最终 ts=an11(modn)t_s = a^{n-1} \equiv 1 \pmod n(费马小定理)。既然序列从"非 ±1\pm 1"变到了"11",那么在某个位置必然发生了 n1n-111 的跳变(因为 n1n-111 在模素数下唯一的另一个平方根)。

      即:存在某个 j[1,s]j \in [1, s],使得

      $$t_{j-1} \equiv n-1 \pmod n \quad \text{且} \quad t_j = t_{j-1}^2 \equiv 1 \pmod n$$

    合并结论:若 nn 是素数,则对任意基底 aa,序列 {ti}\{t_i\} 中要么 t0±1t_0 \equiv \pm 1,要么存在某个 tjn1t_j \equiv n-10j<s0 \le j < s)。

    对上述结论取逆否命题,就得到了 Miller-Rabin 的合数判定条件:

    若对某个基底 aa,序列 {ti}\{t_i\} 满足:

    1. t0≢1t_0 \not\equiv 1t0≢n1t_0 \not\equiv n-1
    2. 对所有 j[1,s]j \in [1, s]tj≢n1t_j \not\equiv n-1

    nn 一定是合数

    若所有选定基底都不触发上述合数条件,则 nn 为素数(在确定性基底集下可严格证明)。

    算法逻辑:

    对每个选定的基底 pp ,令 t=pdmodnt = p^d \bmod n ,d 为 n1n−1 去掉所有因子 22 后剩下的奇数部分:

    1. t=1t=1t=n1t=n-1,本轮通过。
    2. 否则,将 tt 反复平方最多 ss 次。若某次平方后得到 n1n-1,本轮通过。
    3. 若始终没得到 n1n-1,则 nn 是合数。
    4. 若所有选定基底都通过,则 nn 是素数。

    代码中选用了前 15 个素数 {2,3,5,7,11,13,17,19,23,29,31,37,41,43,47} 作为测试基底。前 15 个素数的基底已被数学证明:在 __int128 范围内该测试是绝对安全的。

    算法时间复杂度为 O(Klog2N)O(Klog^2N) ,其中 KK 为基底。

    代码

    #include<bits/stdc++.h>
    #define int __int128 // 扩展精度,支持约1.7×10^38范围
    using namespace std;
    
    // 确定性测试基底,覆盖__int128范围
    const int prime[]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47};
    
    template<typename T>inline void qr(T &x){
    	int f=1;x=0;char c=getchar();
    	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    	x*=f;
    }
    
    template<typename T>inline void qw(T x){
    	if(x<0) putchar('-'),x=-x;
    	if(x/10) qw(x/10);
    	putchar(x%10+'0');
    }
    
    inline int qpow(int a,int b,int m){
        int res=1; a%=m;
        for(;b;b>>=1,a=(a*a)%m)
            if(b&1) res=(res*a)%m;
        return res;
    }
    
    // Miller-Rabin确定性素性测试
    inline bool Is_prime(int x){
    	if(x<2) return false;
    	// 小数直接查表
    	if(x<=37){
    		for(int p:prime) if(x==p) return true;
    		return false;
    	}
    	// 分解x-1 = 2^s * d
    	int d=x-1,s=0;
    	while(!(d&1)) d>>=1,s++;
    	// 逐个基底测试
    	for(int y:prime){
    		if(y>=x) break;
    		int t=qpow(y,d,x);
    		// 首项为1或x-1,本轮通过
    		if(t==1||t==x-1) continue;
    		// 二次探测:反复平方检查是否出现x-1
    		bool flg=false;
    		for(int j=1;j<=s;j++){
    			t=t*t%x;
    			if(t==x-1){ flg=true; break; }
    		}
    		// 未通过二次探测,必为合数
    		if(!flg) return false;
    	}
    	return true; // 所有基底通过,确定为素数
    }
    
    signed main(){
    	ios::sync_with_stdio(false);
    	cin.tie(0),cout.tie(0);
    	int T; qr(T);
    	while(T--){
    		int n; qr(n);
    		puts(Is_prime(n)?"Yes":"No");
    	}
    }
    
    • @ 2026-8-17 9:20:49

      十年OI一场空,不开__int128见祖宗

  • -1
    @ 2026-8-10 10:49:41
    #include<bits/stdc++.h>
    #include "prime.hpp"
    using namespace std;
    int main()
    {
    	int q;cin>>q;
    	while(q--)
    	{
    		long long n;cin>>n;
    		if(is_prime(n))puts("Yes");
    		else puts("No");
    	}
    	return 0;
    }
    
  • 1

信息

ID
3250
时间
1000ms
内存
1024MiB
难度
7
标签
(无)
递交数
45
已通过
10
上传者