欧拉函数
题目:codevs 4939 欧拉函数
题目描述:
输入一个数n,输出小于n且与n互素的整数个数。
题解:
一道裸体,ϕ(n)=n∗(1−1p1)∗(1−1p2)∗⋯∗(1−1pk)\phi(n)=n*(1-\frac1{p_1})*(1-\frac1{p_2})*\dots*(1-\frac1{p_k})ϕ(n)=n∗(1−p11)∗(1−p21)∗⋯∗(1−pk1),所以现在就需要将n进行质因数分解,再去重,套公式就行了。质因数分解的方式为:用pollard rho算法来寻找因数,并用miller rabin测试素数。
pollard rho
对于一个大整数n,如果任意找一个数xxx使得xxx是nnn的质因数,这样的概率很小,但如果通过一些奇技淫巧取两个数x1x_1x1以及x2x_2x2,使得它们的差是nnn的因数的概率就提高了,如果取x1x_1x1以及x2x_2x2使得gcd(abs(x1−x2),n)>1gcd(abs(x_1−x_2),n)>1gcd(abs(x1−x2),n)>1的概率就更高了。这就是Pollard-Rho算法的主要思想。而选取x1x_1x1和x2x_2x2的奇技淫巧就是先令x1=x2=rand(0x_1=x_2=rand(0x1=x2=rand(0~n−1)n-1)n−1),然后将x1x_1x1按照x1=(x12+c)%nx_1=(x_1^2+c)\%nx1=(x12+c)%n来进行递推(c也是随机数),同时x2x_2x2按照倍增的规律来用x1x_1x1赋值,最终会出现两种结果,一种是找到因数返回,另一种是陷入循环,此时就只需要判断x1==x2x_1==x_2x1==x2时就退出,换一个c来重新寻找。
miller rabin
基本原理就是费马小定理,若p为质数,那么ppp满足ap−1moda^{p-1}modap−1mod p=1p=1p=1,aaa为与p互素的整数。但是p不为素数时也有可能满足这个条件,这样的p就是伪素数。不过可以确定的是如果不满足这个条件,那么这个数一定不是素数。所以我们可以选取多个aaa对p进行测试,只要有一次没有通过测试,那么p就不是素数,如果全部通过的话,那ppp就极有可能是素数,我们就干脆默认ppp是素数的,出错的几率可以忽略不计,并且测试次数越多,正确率越高。
还有一个小优化:将p−1p-1p−1中222的倍数提出来,即p−1=2k∗tp-1=2^k*tp−1=2k∗t,此处t为奇数。于是就可以先把ata^tat modmodmod ppp算出来,再不断平方,如果第某次(包括0次)平方后等于p−1p-1p−1(即在模ppp的情况下的−1-1−1),则测试成功。如果一开始等于111,也测试成功。其它情况都是测试不成功。
代码:
#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。。。