欧拉函数
题目: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∗(1−p11)∗(1−p21)∗⋯∗(1−pk1),所以现在就需要将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(x1−x2),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) n−1),然后将 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
ap−1mod
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
p−1中
2
2
2的倍数提出来,即
p
−
1
=
2
k
∗
t
p-1=2^k*t
p−1=2k∗t,此处t为奇数。于是就可以先把
a
t
a^t
at
m
o
d
mod
mod
p
p
p算出来,再不断平方,如果第某次(包括0次)平方后等于
p
−
1
p-1
p−1(即在模
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。。。