「欧拉函数」学习笔记

前知导入

唯一分解定理

对于任何一个大于 1 的正整数都能分成有限个质数的乘积

即若 n 为大于 1 的整数,则有:n=i=1mpici

其中,pi 为质数且递增,pin,ci0


容斥原理

在此仅做简单引入

带入情景,班里有 10 个同学喜欢数学,5 个喜欢语文,8 个喜欢英语,求至少喜欢一门的学生个数

这个问题很好解决,讲喜欢这三科的学生的集合分别记作 A,B,C,则至少喜欢一门的学生表示为 |ABC|

|ABC|=|A|+|B|+|C||AB||AC||BC|+|ABC|

这就是简单的容斥原理


欧拉函数定义

写作 φ(n),表示小于等于 n 的正整数中与 n 互质的个数


φ(n)通式

φ(n)={1n=1n×pp,p|n(11p)i1


  • n>1 时,根据唯一分解定理,设 n=i=1mpici其中 pi,pjn 的质因子

    1n 中,pi 的倍数有 npi 个,pj 的倍数有 npj

    根据容斥原理,1n 中不与 n 有共同因子 pipj 的个数为 nnpinpj+npi×pj=n×(11pi1pj+1pi×pj)=n×(11pi)×(11pj)

    以此类推,将 n 的全部质因子全部代入容斥原理,即可得到 φ(n)


    1. 线性筛法求 1n 的欧拉函数

      时间复杂度 O(n)

    void euler(int n)
    {
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
    if(!vis[i])
    len++,
    prime[len]=i,
    phi[i]=i-1;
    for(int j=1;j<=len&&i*prime[j]<=n;j++)
    {
    vis[i*prime[j]]=1;
    if(!(i%prime[j]))
    {
    phi[i*prime[j]]=phi[i]*prime[j];
    break;
    }
    else phi[i*prime[j]]=phi[i]*(prime[j]-1);
    }
    }
    }

    1. 求单个数的欧拉函数

      先对其分解质因数,然后带入公式

      单个数的复杂度为 O(n)

    int phi(int x)
    {
    int ret=x;
    for(int i=2;i<=sqrt(n);i++)
    if(x%i==0)
    {
    ret=ret/i*(i-1);
    while(x%i==0) x/=i;
    }
    if(x>1) ret=ret/x*(x-1);
    return ret;
    }

性质

  1. n 为质数时,φ(n)=n1
    • 显然

  1. 欧拉函数是积性函数

    • gcd(a,b)=1 时,φ(a×b)=φ(a)×φ(b)

    • 由此不难推出,当 n 为奇数时,φ(2n)=φ(n)

    • 证明:

      根据唯一分解定理,不妨设 a=i=1mpici,b=i=1mqidi

      因为 gcd(a,b)=1,所以一定不存在一组 pi=qj

      那么 ab 可分解为 i=1mpici×i=1mqidi,其中 p,q 一定不相等

      故此,依据性质 2,则有 φ(ab)=ab×i=1n(11pi)×i=1m(11qi)

      又因为 φ(a)=a×i=1n(11pi),φ(b)=b×i=1m(11qi)

      所以得到 φ(ab)=φ(a)×φ(b)


  1. 欧拉反演

    n=d|nφ(d)=d|nφ(nd)

    • 根据约数 d,nd 的对称性

  1. n=pkk 为质数,那么 φ(n)=pkpk1
    • 根据定义可得

  1. n 为正整数,则满足

    i=1ni[gcd(i,n)=1]={1n=1n×φ(n)2i1

    • 但在这里显然,当 n=1 时下面那个式子是不满足的,因此通式可以写成

    i=1ni[gcd(i,n)=1]=n×φ(n)+12


相关知识扩展

欧拉定理

若正整数 a,b 满足 gcd(a,b)=1 ,则 aφ(m)1(modm)

  • 在此只做描述,暂时未学费马小定理,不会证

扩展欧拉定理

ab={abmodφ(p)gcd(a,p)=1abgcd(a,p)1,b<φ(p)         (modp)abmodφ(p)+φ(p)gcd(a,p)1,bφ(p)


例题

1. 仪仗队

  • 如图:

    image

    这是一个正方形,不放将他看做一个三角形,再乘以 2

    以左下角点为原点 (0,0) 建立坐标系

    设一点 (x,y),当 x,y 互质时,即 gcd(x,y)=1 时,他可以被看到

    反之,则一定有一点 (xgcd(x,y),ygcd(x,y)) 将他挡住

    问题可转化为:i=1(n1) 中分别与 0(i1) 互质的个数

    也就是求 2×i=1n1φ(i)+1

    因为欧拉函数求的是 1(i1) 中与 i 互质的个数,但这里是从 0 开始的,所以纵坐标是 0 的这一条线上 (1,0) 是可以看到的,后面都挡住了,所以要 +1×2 显然,因为我们是将他看做一个三角形去做的

    注意事项:1(n1) 而不是 1n,因为我们的第一列在坐标轴中视作 y=0


  • #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    using namespace std;
    const int N=4e4+10;
    template<typename Tp> inline void read(Tp&x)
    {
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
    }
    int n,len,ans,phi[N],prime[N],vis[N];
    void euler(int n)
    {
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
    if(!vis[i])
    len++,
    prime[len]=i,
    phi[i]=i-1;
    for(int j=1;j<=len&&i*prime[j]<=n;j++)
    {
    vis[i*prime[j]]=1;
    if(!(i%prime[j]))
    {
    phi[i*prime[j]]=phi[i]*prime[j];
    break;
    }
    else phi[i*prime[j]]=phi[i]*(prime[j]-1);
    }
    }
    }
    signed main()
    {
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    euler(n);
    for(int i=1;i<n;i++) ans+=phi[i];
    cout<<(ans<<1)+1;
    }
    • 这里采用筛法求 1(n1) 的欧拉函数,复杂度 O(n),因为要求 1(n1) 的欧拉函数的和

2. Longge 的问题

  • Longge

  • i=1ngcd(i,n)

    (1)i=1ngcd(i,n)(2)=d|ndi=1n[gcd(i,n)=d](3)=d|ndi=1nd[gcd(i,nd)=1](4)=d|nd×φ(nd)=d|nnd×φ(d)dnd

    由此求即可


  • #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    using namespace std;
    template<typename Tp> inline void read(Tp&x)
    {
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
    }
    int n,len,ans;
    int phi(int x)
    {
    int ret=x;
    for(int i=2;i<=sqrt(n);i++)
    if(x%i==0)
    {
    ret=ret/i*(i-1);
    while(x%i==0) x/=i;
    }
    if(x>1) ret=ret/x*(x-1);
    return ret;
    }
    signed main()
    {
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    for(int i=1;i<=sqrt(n);i++)
    if(n%i==0)
    if(i*i!=n) ans+=i*phi(n/i),ans+=(n/i)*phi(i);
    else ans+=i*phi(i);
    cout<<ans;
    }
    • 根据 d,nd 的对称性,只需要 O(n) 即可

3. Shadow公主的诱惑

  • 简化题意:求 i=1n![gcd(i,m!)=1]

    (5)i=1n![gcd(i,m!)=1](6)=n!m!φ(m!)(7)=n!m!×m!×pp,p|m!(p1p)(8)=n!×pp,p|m!(p1p)(9)=n!×ppm(p1p)

    由此求即可


  • 多组测试数据,所以要先预处理(递推||筛法),不然显然会超时

    先设一个 N ,即数据范围,输入中给了总的 modP

    根据上面推出的式子,需要先处理 质数阶乘乘法逆元ipN(i1i)

    • 质数是为了处理 ipN(i1i)
    • 阶乘显然是为了处理 n!的,别忘了 modP
    • 至于乘法逆元,因为在 modP 意义下,直接除以 i 是不行的,要用到 i1(modP)
    • 之后就可以处理 ipN(i1i)

    这样对于每一组输入,直接 O(1) 输出即可


  • Hack

    n,m 中存在整除 R 时,式子出来就是 0 了,但显然不应该是 0

    对此,当循环到 R|i 时,将 i 中的 R 因子全部消掉,即

    int x=i;
    while(x%p==0) x/=p;

    在下面的代码中有体现

    同时,当 n>=Rm<R 时,答案显然是 0,需特判一下

    if(n>=p&&m<p) puts("0");

  • #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    using namespace std;
    const int N=1e7+10;
    template<typename Tp> inline void read(Tp&x)
    {
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
    }
    int t,p,n,m,jc[N],ans[N],inv[N];
    bool pri[N];
    void niyuan()
    {
    inv[1]=1;if(n>=p&&m<p) puts("0");
    for(int i=2;i<N;i++)
    {
    int x=i;
    while(x%p==0) x/=p;
    inv[i]=((p-p/x)*inv[p%x])%p;
    }
    }
    void prime()
    {
    for(int i=2;i<sqrt(N);i++)
    if(!pri[i])
    for(int j=2;i*j<N;j++)
    pri[i*j]=1;
    }
    void prod()
    {
    jc[1]=1;
    for(int i=2;i<N;i++)
    {
    int x=i;
    while(x%p==0) x/=p;
    jc[i]=(jc[i-1]*x)%p;
    }
    }
    void solve()
    {
    ans[1]=1;
    for(int i=2;i<N;i++)
    if(!pri[i])
    {
    int x=i,y=i-1;
    while(x%p==0) x/=p;
    while(y%p==0) y/=p;
    ans[i]=(ans[i-1]*y)%p,ans[i]=(ans[i]*inv[x])%p;
    }
    else ans[i]=ans[i-1];
    }
    signed main()
    {
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(t),read(p);
    niyuan(),prime(),prod(),solve();
    while(t--)
    {
    read(n),read(m);
    if(n>=p&&m<p) puts("0");
    else cout<<(jc[n]*ans[m])%p<<endl;
    }
    }
    • 此处注意数组开的是 [N],预处理的时候不能处理到 N 而应是 N1,不然会炸数组

4. GCD SUM

  • GCDSUM(P2398)

  • (10)i=1nj=1ngcd(i,j)(11)=i=1nj=1nd|i,d|jφ(d)(12)=d=1nφ(d)i=1n[d|i]j=1n[d|j](13)=d=1nφ(d)×nd×nd(14)=d=1nφ(d)×nd2


5. 能量采集

  • (P1447)

  • i=1nj=1mgcd(i,j)

    看起来十分眼熟,和第 4GCD SUM 是十分像的,借鉴那道题,则这道题的式子为:

    (15)ans(16)=i=1nj=1mgcd(i,j)(17)=i=1nj=1md|i,d|jφ(d)(18)=d=1min(n,m)φ(d)i=1n[d|i]j=1m[d|j](19)=d=1min(n,m)φ(d)×nd×md

    但是仔细阅读题面,他并不是上面的式子,拿 (4,8) 打比方,他前面有 (1,2)(2,4)(3,6) 三个点,所以该点的能量损失为 2×3+1=7,而 gcd(4,8)=4 所以要求的实际上应该是

    i=1nj=1m2×(gcd(i,j)1)+1=i=1nj=1m2×gcd(i,j)1

    那么简单转化一下即可,即 ans×2n×m


6. 公约数的和

  • (P1390)

  • i=1nj=i+1ngcd(i,j)

    可以借鉴我们上面这道题

    i=1nj=1ngcd(i,j)=i=1nj=i+1ngcd(i,j)+i=1nj=1i1gcd(i,j)+i=1ngcd(i,i)

    其中,i=1nj=1i1gcd(i,j)i=1nj=i+1ngcd(i,j) 显然是相等的

    i=1ngcd(i,i)=i=1ni=n×(n+1)2

    所以 i=1nj=i+1ngcd(i,j)=i=1nj=1ngcd(i,j)n×(n+1)22

    再套用上面那道题的式子即可


7. Same~GCDs

  • Same~GCDs(CF1295D)

  • x=0m1[gcd(a,m)=gcd(a+x,m)]

    如果将 a,m 分别分解质因数的话,gcd(a,m) 即他们公共的那一部分,不难发现,x 需满足 gcd(a,m)|x 且不能包含 m 除去 gcd(a,m) 的那一部分质因数

    gcd(a,m)=gcd(x,m)

    (20)x=0m1[gcd(a,m)=gcd(a+x,m)](21)=x=0m1[gcd(a,m)=gcd(x,m)](22)=x=0m1[gcd(xgcd(a,m),mgcd(a,m))=1](23)=φ(mgcd(a,m))

    由此求即可


8. 疯狂LCM

  • LCM(P1891)

  • lcm 是最小公倍数的意思,lcm(a,b)=a×bgcd(a,b)

    (24)i=1nlcm(i,n)(25)=i=1ni×ngcd(i,n)(26)=n×i=1nigcd(i,n)(27)=n×d|ni=1nid[gcd(i,n)=d](28)d(29)=n×d|ni=1nid[gcd(id,nd)=1](30)iid(31)=n×d|ni=1ndi[gcd(i,nd)=1](32)5(33)=n×d|n1+d×φ(d)2

    由此求即可


  • 多组测试数据,需要预处理

    欧拉函数筛法预处理,之后在线性预处理 d|n1+d×φ(d)2,之后 O(1) 输出即可

    预处理代码如下:

    void euler(int n)
    {
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
    if(!vis[i])
    len++,
    prime[len]=i,
    phi[i]=i-1;
    for(int j=1;j<=len&&i*prime[j]<=n;j++)
    {
    vis[i*prime[j]]=1;
    if(!(i%prime[j]))
    {
    phi[i*prime[j]]=phi[i]*prime[j];
    break;
    }
    else phi[i*prime[j]]=phi[i]*(prime[j]-1);
    }
    }
    }
    void solve(int n)
    {
    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j+=i)
    ans[j]+=(phi[i]*i+1)>>1;
    }

    至于 solve 的那一部分预处理之前没有用过,新学一下

    根据式子 d|n1+d×φ(d)2,带入我们的代码,定满足 i|j,因为是 j+=i


  • #include<bits/stdc++.h>
    #define int long long
    #define endl '\n'
    using namespace std;
    const int N=1e6+1;
    template<typename Tp> inline void read(Tp&x)
    {
    x=0;register bool z=1;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
    }
    int t,n,len,ans[N],prime[N],phi[N];
    bool vis[N];
    void euler(int n)
    {
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
    if(!vis[i])
    len++,
    prime[len]=i,
    phi[i]=i-1;
    for(int j=1;j<=len&&i*prime[j]<=n;j++)
    {
    vis[i*prime[j]]=1;
    if(!(i%prime[j]))
    {
    phi[i*prime[j]]=phi[i]*prime[j];
    break;
    }
    else phi[i*prime[j]]=phi[i]*(prime[j]-1);
    }
    }
    }
    void solve(int n)
    {
    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j+=i)
    ans[j]+=(phi[i]*i+1)>>1;
    }
    signed main()
    {
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    euler(N);
    solve(N);
    read(t);
    while(t--)
    read(n),
    cout<<ans[n]*n<<endl;
    }

9. GCD

  • GCD(P2568)

  • i=1nj=1n[gcd(i,j)P]

    (34)i=1nj=1n[gcd(i,j)P](35)=i=1nj=1ndPn[gcd(i,j)=d](36)=i=1nj=1ndPn[gcd(id,jd)=1](37)=dPni=1ndj=1nd[gcd(i,j)=1](38)=dPn(1+2×i=1ndφ(i))

    至于最后一步怎么出来的

    我们知道 i=1nj=1i[gcd(i,j)=1]=i=1nφ(i)=i=1nj=in[gcd(i,j)=1]

    同时 i=1nj=1n=i=1nj=1i+i=1nj=in

    但有一个例外,当 n=1 时,i=1nj=1n=i=1nj=1i=i=1nj=in

    于是就有了 dPni=1ndj=1nd[gcd(i,j)=1]=dPn(1+2×i=1ndφ(i))

    减去 1 即考虑 nd=1 的那个情况


  • 注意事项

    预处理的时候,数组开的是 N,循环到 N 就炸了!,所以预处理到 N1!!!

    不知道第几次因为这个WA了


posted @   卡布叻_周深  阅读(32)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示