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

1. 前言

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

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

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

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

  • 基本性质 1:若 \(p\) 为质数,那么 \(\varphi(p)=p-1\)。特别的,\(\varphi(1)=1\)
  • 基本性质 2:设 \(n = p^k\)\(p\) 为质数,那么:

    \[\varphi(n)=n-\dfrac{n}{k}=n-p^{k-1}=p^{k-1} \times (p-1)=p^{k-1} \times \varphi(p) \]

  • 基本性质 3:欧拉函数是积性函数。
  • 基本性质 4:对于数 \(n\),将其质因数分解为 \(\prod_{i=1}^{k}{p_i}^{r_i}\),那么:

    \[\varphi(n)=\prod_{i=1}^{k}\varphi(p_i^{r_i})=\prod_{i=1}^{k}(p_i^{r_i-1} \times (p_i-1))=n \times \prod_{i=1}^{k}(1-\dfrac{1}{p_i}) \]

  • 扩展性质 1:设 \(n=a \times b,\gcd(a,b) = d \in N_+\),那么 \(\varphi(n)=\dfrac{\varphi(a) \times \varphi(b) \times d}{\varphi(d)}\)
  • 扩展性质 2:\(\forall n \in N_+,n=\sum\limits_{d|n}{\varphi(d)}\)
  • 扩展性质 3:设 \(n \in N_+,p\) 为质数,那么:

    \[\varphi(n \times p)=\begin{cases}\varphi(n) \times \varphi(p)&p \nmid n\\\varphi(n) \times p&p \mid n\end{cases} \]

  • 扩展性质 4:对于一个数 \(n\)\(n \geq 2\)),所有小于 \(n\) 且与 \(n\) 互质的数的和为 \(\varphi(n) \times \dfrac{n}{2}\)

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

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

2. 练习题

题单:

P2568 GCD

\(Prime\) 为质数集合。

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

\[\sum_{i=1}^{n}\sum_{j=1}^{n}[\gcd(i,j)=k \in Prime] \]

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

\[\sum_{k \in Prime,k<n}\sum_{i=1}^{n}\sum_{j=1}^{n}[\gcd(i,j)=k] \]

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

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

\[\sum_{k \in Prime,k<n}\sum_{i=1}^{n}\sum_{j=1}^{n}([\gcd(\dfrac{i}{k},\dfrac{j}{k})=1] \times [k \mid i] \times [k \mid j]) \]

然后会发现,\(\dfrac{i}{k} \in [1,\left\lfloor\dfrac{n}{k}\right\rfloor]\),于是乎我们可以再对式子做一个转化:

\[\sum_{k \in Prime,k<n}\sum_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{n}{k}\right\rfloor}[\gcd(i,j)=1] \]

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

那么统计一下 \(2\sum\limits_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\varphi(i)-1\) 就可以了(减 1 是因为 \((1,1)\) 会被统计两次),也就是这样:

\[\sum_{k \in Prime,k<n}(2\sum_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\varphi(i)-1) \]

然后 \(O(n)\) 处理一下 \(\varphi(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

法一:

首先先转换一下式子:

\[\sum_{d=1}^{n}\sum_{i=1}^{n}\sum_{j=1}^{n}(d \times [\gcd(i,j)=d]) \]

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

\[\sum_{d=1}^{n}\sum_{i=1}^{n}\sum_{j=1}^{n}(d \times [\gcd(\dfrac{i}{d},\dfrac{j}{d})=1] \times [d \mid i] \times [d \mid j]) \]

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

\[\sum_{d=1}^{n}(d \times \sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}\sum_{j=1}^{\left\lfloor\frac{n}{d}\right\rfloor}[\gcd(i,j)=1]) \]

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

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

\[\sum_{d=1}^{n}(d \times (2\sum_{i=1}^{\left\lfloor\frac{n}{d}\right\rfloor}\varphi(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:\(\forall n \in N_+,n=\sum\limits_{d|n}{\varphi(d)}\)

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

\[\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{d|\gcd(i,j)}\varphi(d) \]

考虑到 \(d \mid \gcd(i,j)\) 的充要条件是 \(d \mid i,d \mid j\),那么式子就可以转化成这样:

\[\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{d \mid i,d \mid j}\varphi(d) \]

再转化一下式子:

\[\sum_{i=1}^{n}\sum_{j=1}^{n}\sum_{d=1}^{n}(\varphi(d) \times [d \mid i] \times [d \mid j]) \]

提前枚举 \(d\) 的求和符号:

\[\sum_{d=1}^{n}(\varphi(d)\sum_{i=1}^{n}\sum_{j=1}^{n}([d \mid i] \times [d \mid j])) \]

\(\sum_{i=1}^{n}\sum_{j=1}^{n}([d \mid i] \times [d \mid j]) = \left\lfloor\frac{n}{d}\right\rfloor^2\),那么最后答案就是:

\[\sum_{d=1}^{n}(\varphi(d) \times \left\lfloor\frac{n}{d}\right\rfloor^2) \]

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

代码见上面的注释部分。

3. 总结

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

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

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

posted @ 2022-04-17 15:04  Plozia  阅读(254)  评论(0)    收藏  举报