欧拉函数及其应用

符号约定

  1. 本文用$(a,b)$表示$a,b$的最大公约数
  2. $[a,b]$表示$a,b$的最小公倍数
  3. 当a=b时,$[a=b]$为1,否则为0
  4. a|b表示a是b的因数

基本概念

定义

n的欧拉函数($ψn$)定义为小于n的,与n互质的数的个数

也可表示为$ψn=\displaystyle \sum^{n}_{i=1}{[(i,n)=1]}$

性质

  1. 若a为质数,则$ψ(a)=a-1$
  2. 若a,b互质,则$ψ(a*b)$=$ψ(a)*ψ(b)$(欧拉函数是积性函数)
  3. 若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);
    }
}

  

  

posted @ 2019-08-13 18:59  linzhuohang  阅读(1261)  评论(0编辑  收藏  举报