欧拉函数(pollard rho+miller rabin)

欧拉函数

题目:codevs 4939 欧拉函数

题目描述:

输入一个数n,输出小于n且与n互素的整数个数。

题解:

一道裸体, ϕ ( n ) = n ∗ ( 1 − 1 p 1 ) ∗ ( 1 − 1 p 2 ) ∗ ⋯ ∗ ( 1 − 1 p k ) \phi(n)=n*(1-\frac1{p_1})*(1-\frac1{p_2})*\dots*(1-\frac1{p_k}) ϕ(n)=n(1p11)(1p21)(1pk1),所以现在就需要将n进行质因数分解,再去重,套公式就行了。质因数分解的方式为:用pollard rho算法来寻找因数,并用miller rabin测试素数。

pollard rho

对于一个大整数n,如果任意找一个数 x x x使得 x x x n n n的质因数,这样的概率很小,但如果通过一些奇技淫巧取两个数 x 1 x_1 x1以及 x 2 x_2 x2,使得它们的差是 n n n的因数的概率就提高了,如果取 x 1 x_1 x1以及 x 2 x_2 x2使得 g c d ( a b s ( x 1 − x 2 ) , n ) > 1 gcd(abs(x_1−x_2),n)>1 gcd(abs(x1x2),n)>1的概率就更高了。这就是Pollard-Rho算法的主要思想。而选取 x 1 x_1 x1 x 2 x_2 x2的奇技淫巧就是先令 x 1 = x 2 = r a n d ( 0 x_1=x_2=rand(0 x1=x2=rand(0~ n − 1 ) n-1) n1),然后将 x 1 x_1 x1按照 x 1 = ( x 1 2 + c ) % n x_1=(x_1^2+c)\%n x1=(x12+c)%n来进行递推(c也是随机数),同时 x 2 x_2 x2按照倍增的规律来用 x 1 x_1 x1赋值,最终会出现两种结果,一种是找到因数返回,另一种是陷入循环,此时就只需要判断 x 1 = = x 2 x_1==x_2 x1==x2时就退出,换一个c来重新寻找。

miller rabin

基本原理就是费马小定理,若p为质数,那么 p p p满足 a p − 1 m o d a^{p-1}mod ap1mod   p = 1 p=1 p=1 a a a为与p互素的整数。但是p不为素数时也有可能满足这个条件,这样的p就是伪素数。不过可以确定的是如果不满足这个条件,那么这个数一定不是素数。所以我们可以选取多个 a a a对p进行测试,只要有一次没有通过测试,那么p就不是素数,如果全部通过的话,那 p p p就极有可能是素数,我们就干脆默认 p p p是素数的,出错的几率可以忽略不计,并且测试次数越多,正确率越高。
还有一个小优化:将 p − 1 p-1 p1 2 2 2的倍数提出来,即 p − 1 = 2 k ∗ t p-1=2^k*t p1=2kt,此处t为奇数。于是就可以先把 a t a^t at  m o d mod mod  p p p算出来,再不断平方,如果第某次(包括0次)平方后等于 p − 1 p-1 p1(即在模 p p p的情况下的 − 1 -1 1),则测试成功。如果一开始等于 1 1 1,也测试成功。其它情况都是测试不成功。

代码:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
const int tm=10;
LL n,f[70],cnt,ans;
LL gcd(LL a,LL b){
	return !b?a:gcd(b,a%b);
}
LL rd(LL a,LL b){
	return (LL)(1.*rand()/RAND_MAX*(b-a)+0.5)+a;
}
LL mul(LL x,LL y,LL mod){
	/*LL res=0;
	for(x%=mod;y;y>>=1,x=2*x%mod)
		if(y&1)res=(res+x)%mod;
	return res;*/
	LL res=x*y-(LL)((long double)x*y/mod+0.5)*mod;
	return res<0?res+mod:res;
}
LL pow(LL x,LL y,LL mod){
	LL res=1;
	for(;y;y>>=1,x=mul(x,x,mod))
		if(y&1)res=mul(res,x,mod);
	return res;
}
LL PR(LL a,LL c){
	LL x=rd(0,a-1),y=x,d=1;
	for(int i=1,k=1;d==1;i++){
		if((x=(mul(x,x,a)+c)%a)==y)return a;
		d=gcd(a,abs(y-x));
		if(i==k){k<<=1;y=x;}
	}return d;
}
bool witness(LL a,LL x){
	LL tmp=x-1,j=0;
	while(!(tmp&1)){tmp>>=1;j++;}
	LL y=pow(a,tmp,x);
	if(y==1||y==x-1)return 1;
	while(j--){
		y=mul(y,y,x);
		if(y==x-1)return 1;
		if(y==1)return 0;
	}return 0;
}
bool MR(LL x){
	if(!(x%2)||x<3)return x==2;
	for(int i=1;i<=tm;i++){
		LL a=rd(2,x-1);
		if(!witness(a,x))return 0;
	}return 1;
}
void decom(LL x){
	if(x==1)return;
	if(MR(x)){f[++cnt]=x;return;}
	LL d=x,c=x-1;
	while(d==x)
		d=PR(x,c--);
	while(!(x%d))x/=d;
	decom(d);decom(x);
}
int main(){
	while(scanf("%lld",&n)&&n){
		cnt=0;ans=n;
		decom(n);
		sort(f+1,f+1+cnt);
		cnt=unique(f+1,f+1+cnt)-(f+1);
		for(int i=1;i<=cnt;i++)
			ans=ans/f[i]*(f[i]-1);
		printf("%lld\n",ans);
	}
	return 0;
}

ps:此题要用一个黑科技来代替慢速乘法,否则会TLE。。。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值