欧拉函数
Posted FallDream
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了欧拉函数相关的知识,希望对你有一定的参考价值。
Phi(x)表示小于等于x的数中与x互质的数的个数。
x的质因数是k1,k2… 则Phi(x)=x*(1-1/k)*(1-1/k2)* …
例如Phi(6)=6*(1-1/3)*(1-1/2) =2
1.很显然,一个质数的Phi值等于它-1
2.如果x是质数的话 Phi(x^k)= x^k-x^k/x=x^k-x^(k-1),因为和这个数不互质的只有x的倍数。
3.欧拉函数是积性函数,如果把x因数分解后x=k1^p1*k2^p2*…*kn^pn
那么Phi(x)=Phi(k1^p1)*Phi(k2^p2)+...+Phi(kn^pn)
这个式子就可以用2中的公式快速计算。
4.假设j是i的质因数
那么i整除j^2时Phi(i)=phi(i/j)*j 因为Phi(i/j)中就已经计算了(1-1/j)
否则i不能整除时j^2时Phi(i)=Phi(i/j)*(j-1) 因为Phi(i/j)中不曾计算(1-1/j),
计算:1.√n暴力 2.筛法的时候,对质数k都枚举它的倍数x之后phi(x)=phi(x)/k*(k-1),复杂度是On的
实现:
1.模板题 hdu2824 求∑Phi(x) l<=x<=r l,r<=3000000
#include<iostream> #include<cstdio> #define ll long long #define MAXN 3000000 using namespace std; ll phi[MAXN+5]; int l,r; int main() { for(int i=1;i<=MAXN;i++)phi[i]=i; for(int i=2;i<=MAXN;i++) if(phi[i]==i) { phi[i]=i-1; for(int j=i<<1;j<=MAXN;j+=i) phi[j]=phi[j]/i*(i-1); } for(int i=1;i<=MAXN;i++)phi[i]+=phi[i-1]; while(scanf("%d%d",&l,&r)!=EOF) cout<<phi[r]-phi[l-1]<<endl; return 0; }
2.poj2480 Longge‘s problem
题意:求∑gcd(i,N) 1<=i<=N N是int
题解:
对于每个它的因数k,gcd(x,N) 的x的个数为Phi(x/k),贡献为k*Phi(x/k),所以可以直接暴力,复杂度大概是√n*因数个数。
那有没有更优秀的做法呢?当然是有的。
设f[n]为题目所求的∑gcd(i,N) 因为gcd是积性函数,又根据一个结论:积性函数求和还是积性函数,所以f(x)也是积性函数。
我们可以把n分解成p1^k1*p2^k2*....*pm^km,则f[n]=f[p1^k1]*...*f[pm^km]
根据f[x]=∑k*Phi(x/k) k|x 我们可以得到:f[p^k]=∑p^i*Phi(P^(k-i)) i=0..k 根据第二点化简一下:
f[p^k]=∑ p^i*(P^(k-i)-P^(k-i-1))(i=1..k)+p^k
=∑(p^k-P^(k-1))(i=1..k)+p^k
=(k+1)*p^k-k*p^(k-1)
这样只要直接因数分解就能非常快的算出答案,最后把所有的f值乘起来就可以了。
#include<iostream> #include<cstdio> #define MAXN 100000 #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘) f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘; ch=getchar();} return x*f; } int n,cnt=0; ll ans=1; int s[MAXN+5]; bool b[MAXN+5]; int pow(int x,int p) { int sum=1; for(int i=x;p;p>>=1,i*=i)if(p&1)sum=sum*i; return sum; } int main() { n=read(); for(int i=2;i<=MAXN;i++) { if(!b[i])s[++cnt]=i; for(int j=1;j<=cnt&&s[j]*i<=MAXN;j++) { b[s[j]*i]=1; if(i%s[j]==0)break; } } for(int i=1;i<=cnt;i++) if(n%s[i]==0) { int num=0; while(n%s[i]==0) {n/=s[i];num++;} ans*=1LL*(num+1)*pow(s[i],num)-1LL*num*pow(s[i],num-1); } if(n>1)ans*=2*n-1; cout<<ans; return 0; }
3.欧拉函数前缀和
原题是这样的:有个n*n的矩阵,每个点有一个灭的灯泡。
你每次可以按下一个灯泡(x,y)的开关,这样的话所有(x*k,y*k),1<=k<=n/x 的灯泡状态都会改变。
就比如n=4,你按下(2,1)就会改变(2,1),(4,2)的状态。
求你最少要按下多少次,可以让所有的灯都亮起来。
题解:
我们发现,对于每个不同的x/y我们都只会按一次,所以我们只会去按那些xy互质的点。
题目转化为有多少对(x,y)是互质的,即(∑Phi(i),2<=i<=n)*2+1(也可以假装Phi(1)是1),就是一道欧拉函数前缀和题目,但n有10^9那么大,无法暴力。
那么我们用F(x)表示有多少个gcd(x,y)=1 1<=y<=x的个数,那么gcd(x,y)=2的个数为F(x/2),gcd=3的个数为F(x/3)....
由于状态总共有x^2种 所以F(x)+F(x/2)+F(x/3)+...+F(x/x)=n^2
也就是说F(x)=n^2-F(x/2)-F(x/3)-...-F(1)
F(i)的i最多只有√n种,所以可以记忆化搜索,据说复杂度是n^(2/3),很科学。
但是这题貌似是某高校的contest,是Private的,所以你也没处交.....所以代码就假装过了懒得写了...
以上是关于欧拉函数的主要内容,如果未能解决你的问题,请参考以下文章