数学/数论专题-专项训练:欧拉函数

1. 前言

本篇博文是欧拉函数的专项训练。

其实一般数论的题目就是推式子难,式子推出来了代码都好打。

如果您没有学过欧拉函数,可以看一看我的这篇博文:数论专题-学习笔记:欧拉函数

这里放一下欧拉函数的 8 个性质:

  • 基本性质 1:若 p 为质数,那么 φ(p)=p1。特别的,φ(1)=1
  • 基本性质 2:设 n=pkp 为质数,那么:

    φ(n)=nnk=npk1=pk1×(p1)=pk1×φ(p)

  • 基本性质 3:欧拉函数是积性函数。
  • 基本性质 4:对于数 n,将其质因数分解为 i=1kpiri,那么:

    φ(n)=i=1kφ(piri)=i=1k(piri1×(pi1))=n×i=1k(11pi)

  • 扩展性质 1:设 n=a×b,gcd(a,b)=dN+,那么 φ(n)=φ(a)×φ(b)×dφ(d)
  • 扩展性质 2:nN+,n=d|nφ(d)
  • 扩展性质 3:设 nN+,p 为质数,那么:

    φ(n×p)={φ(n)×φ(p)pnφ(n)×ppn

  • 扩展性质 4:对于一个数 nn2),所有小于 n 且与 n 互质的数的和为 φ(n)×n2

读者应当对以上内容足够了解且能够独立证明,如不能也可以到上面的博文里面查看证明过程。

那么接下来就开始愉快的推式子吧~

2. 练习题

题单:

P2568 GCD

Prime 为质数集合。

先将题中要求的东西写出来:

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

显然 k[1,n],于是可以将 k 往前移一下:

kPrime,k<ni=1nj=1n[gcd(i,j)=k]

([A] 在这里表示当 A 为真时其值为 1,否则为 0)

然后根据 gcd 的性质转化一下后面两个求和符号:

kPrime,k<ni=1nj=1n([gcd(ik,jk)=1]×[ki]×[kj])

然后会发现,ik[1,nk],于是乎我们可以再对式子做一个转化:

kPrime,k<ni=1nkj=1nk[gcd(i,j)=1]

然后可以发现,如果 gcd(i,j)=1,那么这个在 i=i,j=ji=j,j=i 中都会出现,也就是互质数对出现 2 次。

那么统计一下 2i=1nkφ(i)1 就可以了(减 1 是因为 (1,1) 会被统计两次),也就是这样:

kPrime,k<n(2i=1nkφ(i)1)

然后 O(n) 处理一下 φ(i),做个前缀和就好了。

代码:

/*
========= Plozia =========
    Author:Plozia
    Problem:P2568 GCD
    Date:2021/4/6
========= Plozia =========
*/

#include <bits/stdc++.h>
using std::vector;

typedef long long LL;
const int MAXN = 1e7 + 10;
int n, phi[MAXN];
bool book[MAXN];
LL ans = 0, sum[MAXN];
vector <int> v;

int read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
    return sum * fh;
}

int main()
{
    n = read(); book[1] = 1; sum[1] = phi[1] = 1;
    for (int i = 2; i <= n; ++i)
    {
        if (!book[i]) {phi[i] = i - 1; v.push_back(i);}
        sum[i] = sum[i - 1] + (LL)phi[i];
        for (int j = 0; j < v.size(); ++j)
        {
            if ((LL)i * v[j] > n) break;
            book[i * v[j]] = 1;
            if (i % v[j] == 0) {phi[i * v[j]] = phi[i] * v[j]; break;}
            phi[i * v[j]] = phi[i] * (v[j] - 1);
        }
    }
    for (int i = 0; i < v.size(); ++i) ans += (sum[n / v[i]] << 1) - 1;
    printf("%lld\n", ans); return 0;
}

P2398 GCD SUM

法一:

首先先转换一下式子:

d=1ni=1nj=1n(d×[gcd(i,j)=d])

然后根据 gcd 的性质,稍微转换一下式子:

d=1ni=1nj=1n(d×[gcd(id,jd)=1]×[di]×[dj])

然后将 d 移到前面,同时改变一下枚举顺序:

d=1n(d×i=1ndj=1nd[gcd(i,j)=1])

等等,后面这个不就是上面这道题吗?

套用上面这道题的方法,就可以将式子转成这个:

d=1n(d×(2i=1ndφ(i)1))

然后做一遍前缀和+线性筛就可以了。

代码:

/*
========= Plozia =========
    Author:Plozia
    Problem:P2398 GCD SUM
    Date:2021/4/6
========= Plozia =========
*/

#include <bits/stdc++.h>
using std::vector;

typedef long long LL;
const int MAXN = 1e5 + 10;
int n, phi[MAXN];
bool book[MAXN];
LL ans = 0, sum[MAXN];
vector <int> v;

int read()
{
    int sum = 0, fh = 1; char ch = getchar();
    for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
    for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
    return sum * fh;
}

int main()
{
    n = read(); book[1] = 1; phi[1] = 1; sum[1] = 1;
    for (int i = 2; i <= n; ++i)
    {
        if (!book[i]) {phi[i] = i - 1; v.push_back(i);}
        sum[i] = sum[i - 1] + (LL)phi[i];
        for (int j = 0; j < v.size(); ++j)
        {
            if (i * v[j] > n) break;
            book[i * v[j]] = 1;
            if (i % v[j] == 0) {phi[i * v[j]] = phi[i] * v[j]; break;}
            phi[i * v[j]] = phi[i] * (v[j] - 1); 
        }
    }
    //做法 2
    // for (int i = 1; i <= n; ++i)
    //     ans += (LL)phi[i] * (LL)(n / i) * (LL)(n / i);
    // printf("%lld\n", ans);
    //做法 1
    for (int i = 1; i <= n; ++i)
        ans += i * (2ll * sum[n / i] - 1);
    printf("%lld\n", ans);
    return 0;
}

法二:

记得扩展性质 2 吗?

  • 扩展性质 2:nN+,n=d|nφ(d)

于是式子就可以转化成这样:

i=1nj=1nd|gcd(i,j)φ(d)

考虑到 dgcd(i,j) 的充要条件是 di,dj,那么式子就可以转化成这样:

i=1nj=1ndi,djφ(d)

再转化一下式子:

i=1nj=1nd=1n(φ(d)×[di]×[dj])

提前枚举 d 的求和符号:

d=1n(φ(d)i=1nj=1n([di]×[dj]))

i=1nj=1n([di]×[dj])=nd2,那么最后答案就是:

d=1n(φ(d)×nd2)

于是线性筛求一遍 φ(d),然后直接计算即可。

代码见上面的注释部分。

3. 总结

数论题只需要推出式子,代码就能够写出来。

这次的两道题都是关于 gcd 的,而关于 gcd 的处理方法通常是转化求和符号,转化为形如 gcd(a,b)=1 这样的互质形式,然后采用欧拉函数求解。

当然关于 gcd 的问题更加通用的办法是莫比乌斯反演。

posted @   Plozia  阅读(176)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示