欧拉函数及其应用
符号约定
- 本文用$(a,b)$表示$a,b$的最大公约数
- $[a,b]$表示$a,b$的最小公倍数
- 当a=b时,$[a=b]$为1,否则为0
- a|b表示a是b的因数
基本概念
定义
n的欧拉函数($ψn$)定义为小于n的,与n互质的数的个数
也可表示为$ψn=\displaystyle \sum^{n}_{i=1}{[(i,n)=1]}$
性质
- 若a为质数,则$ψ(a)=a-1$
- 若a,b互质,则$ψ(a*b)$=$ψ(a)*ψ(b)$(欧拉函数是积性函数)
- 若a为质数,b为a的倍数,则$ψ(a*b)=ψ(b)*a$(与b互质的数自然与a互质,b扩大了a倍后与b互质的数+k*b还是与b互质)
快速求解方法
可以通过欧拉筛法在$O(n)$的时间内快速预处理出1~n的所有质数和欧拉函数。
tag标记其是否为合数,f为欧拉函数,p用于存放质数。
void get_euler() { for(int i=2;i<=N;i++) { if(!tag[i])//找到一个新质数 { p[++cnt]=i; f[i]=i-1;//性质1 } for(int j=1;j<=cnt&&i*p[j]<=N;j++) { int v=i*p[j]; tag[v]=true; if(i%p[j]==0) { //此时v=i*p[j] f[v]=f[i]*p[j];//性质3 break;//保证O(n)的时间复杂度 } //因为p[j]的因数除了它和1没有别的了,所以此时p[j]和i互质 f[v]=f[i]*f[p[j]];//性质2 } } }
应用
在gcd,lcm有关的和式推导中有重要作用。
辅助函数
下面的推导需要用到这一函数。
设$f(x)=\displaystyle \sum^{n}_{i=1}{i*[(i,n)==1]}$
相当于求出x以内与x互质的数的和(与欧拉函数区分开)
现在考虑如何在O(1)时间内求解出该函数。
经观察可得,若x>2,则与x互质的数可以头一个尾一个地两两分组,并且每一组的和都是x。
那么原函数可以化为$f(x)={{{ψ(x)} \over{2}}*n}$。
就可以直接计算得出了。
gcd和式
若要计算:
$\displaystyle \sum^{n}_{i=1}{(i,n)}$
可知每个最大公因数一定是n的因数
也就是说我们只要统计每个因数贡献了多少次就行了
对于因数a,若$gcd(a*k,n)=a$
则$gcd(k,n/a)=1$
即,k的数量为n/a内与n/a互质的数,即$\phi(n/a)$
可将其化为:
$\displaystyle \sum^{n}_{a|n}{a*\phi(n / a }$
然后我们就可以先预处理出欧拉函数,然后枚举当前数的每一个因数即可($O(n^{0.5})$)。
lcm和式
若要计算:
$\displaystyle \sum^{n}_{i=1}{[i,n]}$
因为$[i,n]={{i*n} \over {(i,n)}}$,所以可将其化为:
$\displaystyle n* \sum^{n}_{i=1}{{{i} \over {(i,n)}}}$
我们考虑将(i,n)一样的分为一组计算,即枚举每一个(i,n)分别计算贡献
设$d=(i,n)$,j为$i \over d$,则原式化为:
$\displaystyle n* \sum^{n}_{d|n}{ \displaystyle \sum^{{n} \over{d}}_{j=1}{j*[(j,{{n}\over{d}})=1]}}$
联系$f(x)$
$n* \displaystyle \sum^{n}_{d|n}{* f({{n}\over{d}})}$
然后我们就可以先预处理出欧拉函数以及每个数的答案($O(n log n)$),然后回答即可。
代码:
int calc(int n)//计算f(x) { if(n<=2) return 1; return (ll)f[n]/2*(ll)n;//这里的f表示欧拉函数 } int main() { get_euler();//上文的函数 for(int i=1;i<=N;i++)//预处理出每一个数的答案。 { for(int j=1;j<=N/i;j++) { ans[i*j]+=calc(i); } } int t; cin>>t; while(t--) { int n=read(); printf("%lld\n",ans[n]*n); } }