【Codeforces 1091G】New Year and the Factorisation Collaboration

交互题,中国剩余定理

题目大意:

给定正整数 $n$($21\leq n\leq 2^{1024}$),保证 $n$ 能表示成 $\prod\limits_{i=1}^kp_i$ 的形式,其中 $2\leq k\leq 10$,$p$ 是互不相同的 $4n+3$ 形质数。

交互库提供了模 $n$ 意义下的加法、减法、乘法、求逆、求平方根、乘方操作。其中求逆和求平方根在不存在结果时返回 $-1$,存在多个模意义下的平方根时,只返回其中一个,且多次询问结果不会改变

最多进行 $100$ 次交互(包括返回答案),要求对 $n$ 分解质因数。

题解:

交互库提供的操作只有 sqrt 是有用的,因为其他操作我们都可以自己轻松地实现。

考虑如何合理利用 sqrt 这个操作。

我们假设知道 $x^2\equiv x’^2\pmod n$,那么就有 $(x+x’)(x-x’)\equiv 0\pmod n$,即 $(x+x’)(x-x’)=nc$。

对于 $(x+x’)$ 和 $(x-x’)$,排除其中有 $0$ 和 $n$ 的情况,则我们可以将 $n$ 的质因子分成两个集合。

假如我们有很多个不同的集合,就可以通过这些集合(中的质因子的乘积)求 $\gcd$ 的方式来提取出 $n$ 的质因子。

交互库提供的这个 sqrt 操作,我们给定 $y$,它可以帮我们找到一个满足 $x^2\equiv y\pmod n$ 的 $x$,而我们需要两个这样的 $x$。那么我们指定一个 $x$,然后向交互库询问 $x^2\bmod n$ 就可以得到另外一个。

不过这样有可能出现两个 $x$ 相同或相反的情况。我们考虑这件事情发生的概率。

由于满足 $x^2-x’^2\equiv 0\pmod n$,那么对于 $i\in[1,k]$,都有 $x^2-x’^2\equiv 0\pmod{p_i}$。

容易证明,在模奇素数意义下,一个数的二次剩余只有 $0$ 个或 $2$ 个,也就是说指定 $x$ 的情况下,有两个 $x’$ 是满足条件的。

由中国剩余定理可以知道,满足 $x^2-x’^2\equiv 0\pmod n$ 的 $x’$ 有 $2^k$ 个,其中有 $2$ 个不能使用,也就是说随机一个 $x$,有 $1-\frac{1}{2^{k-1}}$ 的概率能得到一个符合条件的 $x’$。

那么我们每次询问时随机 $x$ 然后进行上述操作即可。若随机到的 $x$ 与 $n$ 不互质,则直接分成 $\gcd(x,n)$ 和 $\frac n{\gcd(x,n)}$ 即可。

我们可以进行 $99$ 次询问,可以得到足够多的质因子集合,然后提取出所有的质因子即可。

由于涉及高精度运算,使用 Java 或者 Python 会比较方便。

Code:

import java.util.*;
import java.math.*;
public class Main{
    static Scanner in=new Scanner(System.in);
    static BigInteger n,TWO=BigInteger.valueOf(2);
    static BigInteger A[]=new BigInteger[300],B[]=new BigInteger[300];
    static int k=0,t=0;
    static Random rand=new Random();
    static void solve(BigInteger n){
        BigInteger x,g,xx,y;
        x=new BigInteger(1024,rand);
        x=x.remainder(n.subtract(BigInteger.ONE)).add(BigInteger.ONE);
        g=n.gcd(x);
        if(g.compareTo(BigInteger.ONE)!=0){
            A[++k]=g;
            A[++k]=n.divide(g);
            return;
        }
        y=x.multiply(x).remainder(n);
        System.out.println("sqrt "+y);
        System.out.flush();
        xx=in.nextBigInteger();
        if(x.compareTo(xx)==0||x.add(xx).compareTo(n)==0)return;
        x=x.subtract(xx);
        if(x.compareTo(BigInteger.ZERO)<0)x.add(n);
        g=x.gcd(n);
        A[++k]=g;
        A[++k]=n.divide(g);
    }
    public static void main(String args[]){
        n=in.nextBigInteger();
        A[k=1]=n;
        for(int T=0;T<99;++T)solve(n);
        for(int i=1;i<=k;++i)
            if(A[i].compareTo(BigInteger.ONE)!=0){
                BigInteger g=A[i];
                for(int j=1;j<=k;++j)
                    if(A[j].compareTo(BigInteger.ONE)!=0){
                        BigInteger d=g.gcd(A[j]);
                        if(d.compareTo(BigInteger.ONE)!=0)
                            g=d;
                    }
                B[++t]=g;
                for(int j=1;j<=k;++j)
                    while(A[j].remainder(g).compareTo(BigInteger.ZERO)==0)
                        A[j]=A[j].divide(g);
            }
        System.out.print("! "+t);
        for(int i=1;i<=t;++i)
            System.out.print(" "+B[i]);
        System.out.println();
    }
}