zoukankan      html  css  js  c++  java
  • 「SOL」小A与两位神仙(洛谷)

    博客赶工……


    # 题面

    给定正整数 (P),保证 (P) 为某个素数的幂。给定多组正整数 (x,y),保证 ((x,P)=0,(y,P)=0),求是否存在正整数 (a),使得 (x^aequiv ypmod P)

    数据规模:(Ple10^{18}),询问组数不超过 (2 imes10^4)


    # 解析

    先随便什么方法把 (P) 给分解成 (p^k),可以得到 (varphi(P)=p^{k-1}(p-1)),同时可以说明 (P) 一定有原根,记为 (g)

    于是任何与 (P) 互质的数都可以表示成原根的幂,若 (g^b=a),则记 ({ m ind} a=b)

    根据欧拉定理,可以知道原方程等价于

    [acdot{ m ind} xequiv{ m{ind}} ypmod{varphi(P)} ]

    不妨看看 ( m{ind} x) 有什么性质。对所有 (x) 在模 (P) 意义下的幂定义集合

    [mathbb{S}={x^imod Pmid iinmathbb{N}} ]

    可以知道 (x^i=g^{icdot{ m ind} x}),即 ({ m ind} {x^i}=icdot{ m ind} x),并且 (i) 可以取任意自然数。又因为 (x^{varphi(P)}equiv 1),于是可以得到

    [|mathbb{S}|=frac{varphi(P)}{({ m ind} x,varphi(P))} ]

    (|mathbb{S}|) 就是 (x)(P) 意义下的阶 ({ m ord} x),于是阶一定是 (varphi(P)) 的因数。

    因为 (yequiv x^a),所以 ({ m ind} y=acdot{ m ind} x),那么:

    [egin{array}{l} { m ord} x=frac{varphi(P)}{({ m ind} x,varphi(P))}\ { m ord} y=frac{varphi(P)}{(acdot{ m ind} x,varphi(P))} end{array} ]

    于是「存在 (a) 使得 (x^aequiv y)」等价于「({ m ord} ymid{ m ord} x)」。

    最后一个问题就是,怎么求阶?根据上面的推导以及阶的定义,阶一定是 (varphi(P)) 的因数,而且 (forall { m ord} xmid a,x^aequiv 1)

    所以可以用试除法,设 ({ m ord} x) 的初值 (x_0)(varphi(P)),将其试除一个 (varphi(P)) 的质因子 (p_0),若 (p_0mid x_0)(x^{frac{x_0}{p_0}}equiv 1),则 (x_0:=frac{x_0}{p_0})

    至于怎么找到 (varphi(P)) 的质因子,由于数很大,可以使用 Pollard_rho。


    复习笔记

    # Miller_rabin 素性测试

    检测原理基于「费马定理的逆定理」以及「二次探测」。

    费马定理的逆定理

    若 $a^{p-1}equiv1pmod p$,则 $p$ 可能是素数;若不成立,则 $p$ 一定不是素数。

    二次探测

    若 $x^2equiv 1pmod p$ 的解有且仅有 $xequivpm1$,则 $p$ 可能是素数,否则一定不是素数。

    可以看出基于这两个原理,Miller_rabin 只是一个随机算法,但是它的正确性在 OI 的数据规模内已经够用了~

    如何实现 Miller_rabin?首先试除几个(前10个)小质数,排除掉许多合数,加快算法效率。

    因为已经试除过 (2),此时的 (p) 一定是奇数,可以拆解为 (p=2^mq) 的形式,然后进行二次探测。

    随机一个数 (a),或者 (a) 直接取小质数,通过如下过程实现二次探测:

    • 首先检验 (a^q) 是否为 (pm1),若是,则 (a^q) 本身就是 (x^2equiv1) 的解,满足二次探测;
    • 然后依次检验 (a^{2q},a^{4q},dots,a^{2^mq})
    • 若检验到 (a^{2^iq}) 时,(a^{2^iq}) 第一次等于 (1),此时 (a^{2^{i-1}q} eqpm1),但是 (a^{2^{i-1}q})(x^2equiv1) 的解,不满足二次探测;
    • 若检验到 (2^{2^iq}) 时,(a^{2^iq}) 第一次等于 (-1),那么 (a^{2^iq})(x^2equiv 1) 的解,满足二次探测;
    • 若检验结束后还没有找到 (x^2equiv 1) 的解,即 (a^{2^iq} eqpm1),不满足费马小定理的逆定理。

    具体可以参照「# 源代码」中的 miller_rabin 函数。


    # Pollard_rho 因数分解

    用于找到一个非 (1) 正整数 (P) 的大于 (1) 小于 (P) 的因子,可以辅助分解质因数。

    Pollard_rho 也为一个随机算法,进行一次 Pollard_rho 有可能无法找出这样的因子。

    Pollard_rho 的概率保证主要有以下两点:

    • 随机 (a,b) 计算 (|a-b|),而不是直接随机一个数 (a)(生日悖论,但是我也不是很懂这个);
    • 并不直接判断 (|a-b|)(P) 的因数,而是判断 (|a-b|) 是否与 (P) 互质,若不互质,则它们的最大公约数就是 (P) 的一个大于 (1) 的因子,这样符合条件的 (|a-b|) 就更多了。

    所以实现时,我们采用伪随机数的方式——令伪随机函数 (f(x)=x^2+b)(bin[1,P))

    先随机两个变量 (x_1=x_2),然后每次以不同的倍数对 (x_1,x_2) 进行伪随机化,例如 (x_1:=f(x_1),x_2:=f(f(x_2)))

    可以发现这样 (x_1,x_2) 在模 (P) 意义下一定会产生循环,并且在循环内可能找不到 (|x_1-x_2|)(P) 不互质。这就是 Pollard_rho 是随机算法的原因。

    产生循环时 (x_1=x_2),此时 ((|x_1-x_2|,P)=P),自动退出循环。

    另外,当然不能对一个质数进行 Pollard_rho 算法,因此先用 Miller_rabin 把 (P) 是质数的情况判掉。

    具体可以参照「# 源代码」中的 pollard_rho 函数。


    # 源代码

    点击展开/折叠代码
    /*Lucky_Glass*/
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    template<class T>inline T rin(T &r){
    	int b=1,c=getchar();r=0;
    	while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
    	while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
    	return r*=b;
    }
    typedef long long llong;
    #define con(type) const type &
    
    namespace PRIME{
    	llong ina_gcd(con(llong)a,con(llong)b){return b?ina_gcd(b,a%b):a;}
    	llong ina_abs(con(llong)a){return a<0?-a:a;}
    	llong mul(con(llong)a,con(llong)b,con(llong)mod){return llong((__int128)a*b%mod);}
    	llong ina_pow(llong a,llong b,con(llong)mod){
    		llong r=1;
    		while(b){
    			if(b&1) r=mul(r,a,mod);
    			a=mul(a,a,mod),b>>=1;
    		}
    		return r;
    	}
    	bool miller_rabin(con(llong)x,con(llong)a,llong b){
    		llong tmp=ina_pow(a,b,x);
    		if(tmp==x-1 || tmp==1) return true;
    		while(b!=x-1){
    			tmp=mul(tmp,tmp,x),b<<=1;
    			if(tmp==x-1) return true;
    			if(tmp==1) return false;
    		}
    		return false;
    	}
    	const int PRM[]={2,3,5,7,11,13,17,19,61,24251};
    	bool primeTest(con(llong)x){
    		for(int i=0;i<10;i++)
    			if(x%PRM[i]==0)
    				return x==PRM[i];
    		llong tmp=x-1;
    		while(!(tmp&1)) tmp>>=1;
    		for(int i=0;i<10;i++)
    			if(!miller_rabin(x,PRM[i],tmp))
    				return false;
    		return true;
    	}
    	llong pollard_rho(con(llong)x){
    		for(int i=0;i<10;i++)
    			if(x%PRM[i]==0)
    				return PRM[i];
    		llong x1=rand()%(x-2)+2,x2=x1,tmp=rand()%(x-1)+1,d=1;
    		while(d==1){
    			x1=(mul(x1,x1,x)+tmp)%x;
    			x2=(mul(x2,x2,x)+tmp)%x;
    			x2=(mul(x2,x2,x)+tmp)%x;
    			d=ina_gcd(ina_abs(x1-x2),x);
    		}
    		return d;
    	}
    	void divideNum(con(llong)x,llong *&res){
    		if(x==1) return;
    		if(primeTest(x)){
    			*res=x,res++;
    			return;
    		}
    		llong dv=pollard_rho(x);
    		divideNum(dv,res),divideNum(x/dv,res);
    	}
    }
    
    llong m,phim,dv_phim[100],ena_m[100];
    int ncas,ndv_phim,nena_m;
    
    llong eta_pow(con(llong)x,con(int)k){
    	llong ret=1;
    	for(int i=1;i<=k;i++){
    		__int128 tmp=(__int128)x*ret;
    		if(tmp>m) return m+1;
    		ret=(llong)tmp;
    	}
    	return ret;
    }
    llong kthRoot(con(int)k){
    	llong tmp=pow(m,1.0/k)+2;
    	while(eta_pow(tmp,k)>m) tmp--;
    	return tmp;
    }
    llong divideM(){
    	llong tmp;
    	for(int i=60;i;i--)
    		if(tmp=kthRoot(i)){
    			if(eta_pow(tmp,i)==m)
    				return tmp;
    		}
    	return m;
    }
    void init(){
    	llong pm=divideM();
    	phim=pm-1;
    	for(llong it=pm;it<m;it*=pm)
    		phim*=pm;
    	llong *tmp=dv_phim;
    	PRIME::divideNum(phim,tmp);
    	ndv_phim=int(tmp-dv_phim);
    	sort(dv_phim,tmp);
    	for(int i=0;i<ndv_phim;i++){
    		int j=i;
    		while(j+1<ndv_phim && dv_phim[j+1]==dv_phim[i]) j++;
    		ena_m[++nena_m]=dv_phim[i];
    		i=j;
    	}
    }
    llong ord(con(llong)x){
    	llong ret=phim;
    	for(int i=1;i<=nena_m;i++)
    		while(ret%ena_m[i]==0 && PRIME::ina_pow(x,ret/ena_m[i],m)==1)
    			ret/=ena_m[i];
    	return ret;
    }
    int main(){
    	// freopen("input.in","r",stdin);
    	rin(m),rin(ncas);
    	init();
    	while(ncas--){
    		llong x,y;rin(x),rin(y);
    		printf("%s
    ",ord(x)%ord(y)? "No":"Yes");
    	}
    	return 0;
    }
    

    THE END

    Thanks for reading!

    去听从内心的旨意 去步步为营
    绝望中爆发无限潜力 为弱肉强食

    ——《陷落之序》By 著小生zoki

    > Link 陷落之序-Bilibili

    欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
  • 相关阅读:
    JMeter分布式压测-常见问题之( Cannot start. localhost.localdomain is a loopback address)
    Jmeter实现multipart/form-data类型请求
    【漫画算法最后章节】【算法运用】 —— Bitmap算法的巧用
    整数类型
    宁波多校(一) E题 ddd的逛街计划(Easy Version)
    宁波多校(一) D题 COLORS的字符串挑战(线段树+hash+二分)
    CF1361D Johnny and James(模拟)
    CF1363F Rotating Substrings(dp)
    CF1363E Tree Shuffling(贪心+树上乱搞)
    AcWing1185 单词游戏(欧拉路径)
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14438972.html
Copyright © 2011-2022 走看看