1 条题解

  • 0
    @ 2026-8-15 10:15:58

    本题需要使用 Pollard-Rho 算法。

    算法讲解

    我们需要了解:

    同余的性质:如果两个整数 x,yx, y 满足 xy(modp)x \equiv y \pmod p(即 xxyy 除以 pp 的余数相同),那么它们的差 (xy)(x - y) 一定是 pp 的倍数。

    gcd(xy,N)\gcd(|x - y|, N) 的结果一定是 pp 的倍数。

    ppNN 的因数,

    N=kpN = k \cdot p

    xy(modp)x \equiv y \pmod p

    xy=mpx - y = m \cdot p

    令 $d = \gcd(|x - y|, N) = \gcd(m \cdot p, k \cdot p) = p \cdot \gcd(m, k)$。

    gcd(m,k)=1\gcd(m, k) = 1 时,那么 dd 就等于 pp

    gcd(m,k)1\gcd(m, k) \neq 1 时,ddNN 的一个非平凡因数。


    假设 NN 的最小素因数是 pp。我们在模 pp 的意义下随机选取数字,模 pp 的余数只有 0,1,2,...,p10, 1, 2, ..., p-1pp 种可能。

    根据生日悖论的数学推导:从 pp 个元素中随机抽取,大约只需要抽取 p\sqrt{p} 次,就有超过 50% 的概率出现两个相同的元素(即 xixj(modp)x_i \equiv x_j \pmod p)。

    因为 pNp \le \sqrt{N},所以 pN14\sqrt{p} \le N^{\frac{1}{4}}。 这意味着,我们平均只需要生成 N14N^{\frac{1}{4}} 个数字,就能找到一对满足条件的 (xi,xj)(x_i, x_j)

    我们不能真的用系统随机数,因为我们需要序列具有确定性可重复性,以便进行数学推导。

    我们定义一个简单的二次多项式函数:

    f(x)=(x2+c)modNf(x) = (x^2 + c) \bmod N

    其中 cc 是一个常数(通常 c0,2c \neq 0, -2)。

    我们从起点 x1x_1 开始,不断迭代:

    $$x_2 = f(x_1), \quad x_3 = f(x_2), \quad \dots, \quad x_{k+1} = f(x_k)$$

    由于模 NN 的余数只有 NN 种,这个序列 {xk}\{x_k\} 迟早会出现重复,从而进入循环。

    如图,由于序列排列形成的形状酷似希腊字母 ρ\rho ,所以该算法得名 rho

    如果 xy(modp)x \equiv y \pmod p,那么 f(x)f(y)(modp)f(x) \equiv f(y) \pmod p 。 $f(x) - f(y) = (x^2+c) - (y^2+c) = x^2 - y^2 = (x-y)(x+y)$ 。因为 p(xy)p \mid (x-y),所以 p(f(x)f(y))p \mid (f(x)-f(y))

    这意味着:如果在取模 NN 下还没进环,但在取模 pp 下可能已经进环了。因此我们应该找取模 pp 下的环。我们可以使用 Floyd 判环法 或改进的 Brent 判环法

    Floyd 判环法

    设两个变量 aa(慢指针)和 bb(快指针):

    初始使 a=b=x1a = b = x_1。让 aa 每次走 1 步,即 a=f(a)a = f(a) ,让 bb 每次走 2 步,即 b=f(f(b))b = f(f(b)) 。当 a==ba==b 时意味着我们成功找到环了。

    这个算法 scy 曾经讲过,我就不讲太多了

    Brent 判环法

    floyd 方法每走一步都要算一次 gcdgcd ,而 gcdgcd 本身也有开销。Brent 方法让快指针按 2k2^k 的步长跳跃,减少了迭代次数,比 Floyd 快约 24%。

    倍增优化

    不是每一步都算 gcd\gcd,而是先累积多步的差值乘积 P=xiyimodNP = \prod |x_i - y_i| \bmod N,每隔 kk 步(如 k=128k=128)再算一次 gcd(P,N)\gcd(P, N)。这样把多次 gcd\gcd 合并成一次,进一步降低了常数复杂度。

    代码

    #include<bits/stdc++.h>
    #define int __int128
    #define abs(x) ((x)>=0?(x):-(x))
    using namespace std;
    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())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;
    }
    inline bool Is_prime(int x){ // Miller-Rabin素性测试
    	if(x<2)return false;
    	if(x<=37){
    		for(int p:prime)if(x==p)return true;
    		return false;
    	}
    	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);
    		if(t==1||t==x-1)continue;
    		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;
    }
    inline int f(int x,int c,int m){return (x*x%m+c)%m;} // 伪随机迭代函数
    inline int rho(int n){ // Pollard-Rho找因数
        if(n==4)return 2;
    	int c=rand();
        int x=f(rand(),c,n); // 慢指针
        int y=f(x,c,n);      // 快指针
        for(int m=1;;m=min(m<<1,(int)128)){ // 倍增批量GCD优化
        	if(x==y)break;   // 模n下相遇,探测失败
            int cnt=1;
            for(int i=0;i<m;i++){
                cnt=(cnt*abs(x-y))%n; // 累积差值乘积
                if(!cnt)break;
                x=f(x,c,n);       // 走1步
                y=f(f(y,c,n),c,n);// 走2步
            }
            int d=__gcd(cnt,n); // 批量求GCD
            if(d!=1)return d;   // 找到因数
        }
        return n; // 返回n表示失败
    }
    int ans;
    inline void dfs(int n){ // 递归分解
        int d=rho(n);
        while(d==n)d=rho(n); // 失败则重试
        int d2=n/d;
        if(Is_prime(d))ans=max(ans,d);
        else dfs(d);
        if(Is_prime(d2))ans=max(ans,d2);
        else dfs(d2);
    }
    inline int ga(int x){ // 获取最大素因数
        if(Is_prime(x))return x;
        ans=0;
        dfs(x);
        return ans;
    }
    signed main(){
        srand(time(0));
        int q;
        qr(q);
        while(q--){
            int x;
            qr(x);
            vector<int>v;
            while(x!=1){ // 不断剥离最大素因数
                int p=ga(x);
                v.push_back(p);
                x/=p;
            }
            sort(v.begin(),v.end());
            qw(v.size());
            putchar(' ');
            for(int t:v){
            	qw(t);
    			putchar(' ');
    		}
            puts("");
        }
    }
    
    • 1

    信息

    ID
    3253
    时间
    1000ms
    内存
    1024MiB
    难度
    9
    标签
    递交数
    63
    已通过
    4
    上传者