「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

欢迎转载٩(๑❛ᴗ❛๑)۶,请在转载文章末尾附上原博文网址~
原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/14438972.html