也许,昨天还在跟|

Joy_Dream_Glory

园龄:1年11个月粉丝:40关注:22

欧拉函数与欧拉定理

欧拉函数

定义

欧拉函数,即 φ(n),表示的是小于等于 n 的和 n 互质的数的个数。

n 是质数的时候,显然有 φ(n)=n1

性质

性质1

欧拉函数是积性函数。

积性函数:积性函数是指对于所有互质的整数 ab 都有性质 f(ab)=f(a)f(b) 的数论函数,例如欧拉函数 φ(n),莫比乌斯函数 μ(n),因数个数函数 d(n)
完全积性函数:对于任意整数 ab 有性质 f(ab)=f(a)f(b) 的数论函数。
积性函数的性质:若 f(n)g(n) 都是积性函数,那么以下函数也为积性函数:
h(x)=f(xp)h(x)=fp(x)h(x)=f(x)g(x)h(x)=d|xf(d)g(xd)

对于欧拉函数,如果有 gcd(a,b)=1,那么 φ(a×b)=φ(a)×φ(b)

特别地,当 n 是奇数时有 φ(2n)=φ(n)

性质2

n=d|nφ(d)

此性质为欧拉反演的定义。

证明:

显然对于一个 i(1in)ingcd 都是唯一的。由此可得: n=d|ni=1n[gcd(i,n)=d]

将公式变形,得到:n=d|ni=1n[d | i,gcd(id,nd)=1]

发现内层循环就是求在 nd 的范围内与其互质的数的个数,即 φ(nd)。因此可将公式变形为 n=d|nφ(nd)

由于 d | n,因此 dnd 一一对应。因此可得到最终公式 n=d|nφ(d)

证毕。

性质3

n=pk,其中 p 是质数,那么 φ(n)=pkpk1

证明:相当于找在 pk 中有多少个数是 p 的倍数,即 pkp,即 pk1,那么 φ(n)=pkpk1。证毕。

性质4

由整数的惟一分解定理,设 n=i=1spiαi,其中 pi 是质数,由 φ(n)=n×i=1spi1pi

惟一分解定理有多种指代意义:整数惟一分解定理,多项式惟一分解定理,交的惟一分解定理,乘积的惟一分解定理。
整数的惟一分解定理,又称算术分解定理,是数论的重要理论之一。该定理可以表述为:对于任意一个大于 1 的整数 n 都可以分解为若干个素因数的连乘积,如果不计各个素因数的顺序,那么这种分解是唯一的。
即若有 n>1,则有 n=p1α1p2α2pkαk,其中 pi 为素数,αi 为正整数。

证明:由整数的惟一分解定理与性质 3,可得:φ(n)=i=1sφ(piαi)=i=1spiαi×(11pi)=n×i=1s(11pi)=n×i=1spi1pi

求欧拉函数

求单个数的欧拉函数值

例题:SP4141 ETF - Euler Totient Function

根据性质 4 暴力求解即可,即质因数分解。有能力的可以学习 Pollard Rho 算法优化(等没事干了再学,别点偏了)。

纷总总兮九州,何寿天兮在予。
#include <bits/stdc++.h>
using namespace std;
int T,n,ans;
int main(){
scanf("%d",&T);while(T --){
scanf("%d",&n);ans = n;
for(int i = 2;i * i <= n;i ++){
if(n % i == 0){
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1) ans = ans / n * (n - 1);
printf("%d\n",ans);
}
return 0;
}

线性筛法求多个数的欧拉函数值

我们先回顾一下线性筛法筛质数。我们筛质数的思路即是,保证每一个合数都只被筛到一次,并且只被它的最小质因子筛到:

愿世间不再有无归之人。
void getPrime(){
int cnt = 0;vis[1] = 1;
for(int i = 2;i <= n;i ++){
if(vis[i] == 0) Prime[++cnt] = i;
for(int j = 1;j <= cnt and i * Prime[j] <= n;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0) break;
}
}
}

那么在线性求欧拉函数的得时候,基本框架还是上面的东西,不过我们需要另外处理一些东西:

我们令 an 的最小质因子,n=na,那么线性筛的过程中 n 会通过 n=n×a 的形式筛掉。

如果说 nmoda=0,那么 n 的所有质因子就和 n 一样。那么 φ(n)=n×i=1spi1pi=a×n×i=1spi1pi=a×φ(n)

否则,此时 na 就是互质的,那么根据性质一,可得 φ(n)=φ(a)×φ(n)=(a1)×φ(n)

司命者,当无情,有情即孽。
void getPhi(){
int cnt = 0;vis[1] = 1;phi[1] = 1;
for(int i = 2;i <= n;i ++){
if(vis[i] == 0) Prime[++cnt] = i,phi[i] = i - 1;
for(int j = 1;j <= cnt and i * Prime[j] <= n;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * phi[Prime[j]];//or phi[i] * (Prime[j] - 1)
}
}
}

习题

1. POJ 2478

线性求欧拉函数板子题。

跨过忘忧渡口,从此生死两隔。
#include <iostream>
#include <cstdio>
#define N 1000006
#define int long long
using namespace std;
int n,vis[N],phi[N],Prime[N],cnt;
void getPhi(){
vis[1] = 1;
for(int i = 2;i <= 1000000;i ++){
if(!vis[i]) phi[i] = i - 1,Prime[++cnt] = i;
for(int j = 1;j <= cnt && Prime[j] * i <= 1000000;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
}
}
for(int i = 2;i <= 1000000;i ++) phi[i] += phi[i - 1];
}
signed main(){
getPhi();while(cin >> n){
if(n == 0) return 0;
printf("%lld\n",phi[n]);
}
}

2. P2158 [SDOI2008] 仪仗队

容易发下左上角可右下角是对称的,因此我们劈开一半只看右下方。

我们以 C 君的位置为原点建立平面直角坐标系。容易得到结论,当且仅当 gcd(i,j)=1 的时候,这个人才不会被前面的人挡住。因此右下方的计算方法为,对于每一个 1in1,求出其对应的 φ(i),相加,即转化为线性求欧拉函数值。然后稍微处理一下结果即可得到答案。并注意特判 n=1 的情况。

忘忧沼泽,是所有生命的归处,既结束,亦开始。
#include <bits/stdc++.h>
#define N 40005
using namespace std;
int n,vis[N],phi[N],cnt,num,Prime[N];
void Getphi(){
int cnt = 0;vis[1] = 1,phi[1] = 1,num = 1;
for(int i = 2;i <= n - 1;i ++){
if(vis[i] == 0) Prime[++cnt] = i,phi[i] = i - 1;
num += phi[i];
for(int j = 1;j <= cnt and Prime[j] * i <= n;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
}
}
}
int main(){
scanf("%d",&n);Getphi();
if(n == 1) printf("0");
else printf("%d",num * 2 + 1);
return 0;
}

3. P2568 GCD

思路很明显,我们先利用线性求欧拉函数计算出 gcd(x,y)=1 的,然后再分别让 x,y 乘上一个素数,那就满足了 gcd(x,y) 是一个素数。注意不要超过 n,然后再自己进行一些细节处理即可。注意开 long long

烈阳灼身,寒风刺骨,皆是无归之痛。
#include <bits/stdc++.h>
#define N 10000007
#define int long long
using namespace std;
int n,num,cnt,vis[N],phi[N],Prime[N];
void getPhi(){
vis[1] = 1,phi[1] = 1;
for(int i = 2;i <= n;i ++){
if(vis[i] == 0) Prime[++cnt] = i,phi[i] = i - 1;
for(int j = 1;j <= cnt and Prime[j] * i <= n;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
}
}
}
signed main(){
scanf("%lld",&n);getPhi();
for(int i = 2;i <= n;i ++) num += (upper_bound(Prime + 1,Prime + cnt + 1,n / i) - Prime - 1) * phi[i];
printf("%lld",num * 2 + cnt);
return 0;
}

4. P2303 [SDOI2012] Longge 的问题

本题即为欧拉反演,即性质 2 的应用。

推导:

令答案为 ans。我们对 gcd(i,n) 进行欧拉反演,得 ans=i=1nd|gcd(i,n)φ(d)

观察到,若 d | gcd(i,n),那么满足它的充要条件为 d | id | n。因此得到 ans=i=1nd|i,d|nφ(d)

将内层循环移至外层,得到 ans=d|ni=1nφ(d)[d | i]。紧接着得到 ans=d|nφ(d)i=1n[d | i]

易得,在 n 以内 d | i 的个数就是 nd。因此我们最终得到 ans=d|nndφ(d)

推导毕。

时间复杂度分析:

我们枚举因数的时间复杂度是 O(n),求一个因数的欧拉函数的复杂度是 O(n),令 S= 因数个数,那么总时间复杂度是 O(S×n)

根据惟一分解定理,我们对 n 进行质因数分解:n=p1r1p2r2pkrk

n 的因数最多的时候,r1=r2==rk=1。而在 232 的范围内,满足条件的最大的数为 223092870=2×3×5×7×11×13×17×19×23

因此因数个数为 29,因此本题的时间复杂度上线为 O(29×n),足以通过此题。

天地之间,无法消散的执念,便化作恶灵。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n,num;
int getPhi(int x){
int ans = x;
for(int i = 2;i * i <= x;i ++){
if(x % i == 0){
ans = ans / i * (i - 1);
while(x % i == 0) x /= i;
}
}
if(x > 1) ans = ans / x * (x - 1);
return ans;
}
void Decom(){
for(int i = 1;i * i <= n;i ++){
if(n % i == 0){
if(i * i == n) num += i * getPhi(i);
else num += (n / i) * getPhi(i) + i * getPhi(n / i);
}
}
}
signed main(){
scanf("%lld",&n);Decom();
printf("%lld",num);
return 0;
}

5. P2398 GCD SUM

本题只不过是上一个题稍微进阶一下,可以尝试自己推一下式子。

易得,原式 =i=1nj=1nd|gcd(i,j)φ(d)=i=1nj=1nd|i,d|jφ(d)=d=1nφ(d)i=1nj=1n[d | i][d | j]=d=1nφ(d)i=1n[d | i]j=1n[d | j]=d=1nφ(d)nd2

推导毕。

云梦之人相信,人死之后,会魂归天生木,再次归家。
#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
int n,num,cnt,vis[N],phi[N],Prime[N];
void getPhi(){
vis[1] = 1,phi[1] = 1,num = n * n;
for(int i = 2;i <= n;i ++){
if(vis[i] == 0) Prime[++cnt] = i,phi[i] = i - 1;
num += phi[i] * (n / i) * (n / i);
for(int j = 1;j <= cnt and Prime[j] * i <= n;j ++){
vis[i * Prime[j]] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
}
}
}
signed main(){
scanf("%lld",&n);getPhi();
printf("%lld",num);
return 0;
}

6. P2350 [HAOI2012] 外星人

本题是对性质 4 理解的一个考察运用,当然直观的,可以看下面提示的那个式子,对做本题来讲更为清晰:φ(i=1mpiqi)=i=1m(pi1)×piqi1

我们知道,在质数中只有 φ(2)=1,那么对于本题来讲,φx(2n) 有最小的 x=n。因为每一次操作最多只能让一个 2 变成 1,并且随即也会有其它的质数减一后再生成一些新的 2。因此我们的思路就是,看看一共能出来多少个 2,就是答案。令 f(i) 表示 i 这个数在整个过程中可以出来多少个 2,那么我们可以得到状态转移方程:

f(i)={f(i1)iPrimef(x)+f(y)x×y=i

显然,如果 i 是质数,那么这一次操作只会让 i 变成 i1,出来的 2 的个数就是 i1 出来的 2 的个数。第二行的式子也显然。

但是我们要注意一个细节问题:如果刚开始这个数 N 没有因子 2,那么需要让最终答案 +1,因为第一次操作没有 2 的减少,只有 2 的生成。

那么,除了上面的特例外,其他情况下,每一次操作一定会减少一个 2,没减少一个 2 也一定对应着一个操作。因此对于每一个质因子,贡献即为 fpi×qi

回首万里,故人长绝。
#include <bits/stdc++.h>
#define N 100005
#define int long long
using namespace std;
int T,num,f[N],vis[N],Prime[N],cnt,m;
void getThrough(){
f[1] = 1,vis[1] = 1;
for(int i = 2;i <= 100000;i ++){
if(!vis[i]) f[i] = f[i - 1],Prime[++cnt] = i;
for(int j = 1;j <= cnt and Prime[j] * i <= 100000;j ++){
vis[i * Prime[j]] = 1;
f[i * Prime[j]] = f[i] + f[Prime[j]];
if(i % Prime[j] == 0) break;
}
}
}
signed main(){
getThrough();scanf("%lld",&T);while(T --){
scanf("%lld",&m);num = 1;
for(int i = 1,p,q;i <= m;i ++){
scanf("%lld%lld",&p,&q);if(p == 2) num --;
num += f[p] * q;
}
printf("%lld\n",num);
}
return 0;
}

欧拉定理

前置 1 简化剩余系的概念

剩余类

定义

剩余类,亦称同余类。设模为 n,则根据余数可将所有的整数分为 n 类,即 0n1,把所有与整数 an 同余的整数构成的集合叫做模 n 的一个剩余类,记作 [a]。并把 a 叫作剩余类 [a] 的一个代表元。

形式化的,即以 Cr(0rn1) 表示所有形如 q×n+r,qZ,则 C0,C1,,Cn1 称为 p 的剩余类。

性质

1. 每一个整数都恰好包含在某一个剩余类 Ci(0in1) 里面。

2. 两个整数 x,y 属于同一类的充要条件是 xy(modn)

完全剩余系

定义

从模 n 的每个剩余类中各取一个数,得到一个由 n 个数组成的集合,叫做模 n 的一个完全剩余系。最常用的完全剩余系是 0,1,,n1

举例:模 4 的一个完全剩余系为 {0,1,2,3},也可以是 {4,5,2,11}

性质

1. 对于 n 个整数,其构成模 n 的完全剩余系等价于其关于模 n 两两不同余。

2.ai(1in) 构成模 n 的一个完全剩余系,则对于任意的 k,mZgcd(k,n)=1,有 k×ai+m(1in) 也为模 n 的完全剩余系。

证明
使用反证法。假设存在 k×aik×aj(1i<jn)(modn),则 n | k×(aiaj)
gcd(k,n)=1n | (aiaj)aiaj(modn)
但是 aiaj(modn),因此假设不成立。因此 k×ai(1in) 两两不同余,+m 后显然成立。
证毕。

3.ai(1in) 构成模 n 的一个完全剩余系,bi(1im) 构成模 m 的一个完全剩余系,且满足 gcd(n,m)=1 时,有 m×ai+n×bj(1in,1jm) 也构成模 n×m 的完全剩余系。

证明
依旧使用反证法。假设存在 m×ai1+n×bj1m×ai2+n×bj2(modn×m)(1i1<i2n,1j1<j2m)
n×m | m×(ai1ai2)+n×(bj1bj2),显然有 n | m×(ai1ai2)+n×(bj1bj2)
gcd(n,m)=1n | (ai1ai2),即 ai1ai2(modn)。同理可证得 bj1bj2(modm)
但是二者均不满足条件,因此假设不成立。因此集合内元素两两不同余。
证毕。

简化剩余系

定义

简化剩余系也称既约剩余系或缩系,是 n 的完全剩余系中与 n 互素的数构成的子集,如果模 n 的一个剩余类里所有数都与 n 互素,就把它叫做与模 n 互素的剩余类。在与模 n 互素的全体剩余类中,从每一个类中各任取一个数作为代表组成的集合,叫做模 n 的一个简化剩余系。

举例:12 的一个简化剩余系是 1,5,7,11

性质

1.ai(1in) 构成模 n 的一个简化剩余系,则对于任意的 k,lZgcd(k,n)=1,有 k×ai+l×n(1in) 也为模 n 的简化剩余系。

证明比较显然。gcd(k,n)=1,gcd(ai,n)=1,因此 gcd(k×ai,n)=1,加上 l×n 后同理。证毕。

2.ai(1in) 构成模 n 的一个简化剩余系,bi(1im) 构成模 m 的一个简化剩余系,且满足 gcd(n,m)=1 时,有 m×ai+n×bj(1in,1jm) 也构成模 n×m 的简化剩余系。

前置 2 关于费马小定理的证明

费马小定理:若 p 为素数,gcd(a,p)=1,那么有 ap11(modp)

证明:

设一个质数为 p,我们取一个不为 p 的倍数的数 a

构造一个序列 A={1,2,,p1}。虽然 A 序列里面没有 0,但是我们把它当做模 p 意义下的一个完全剩余系看待。

那么根据完全剩余系的性质 2,可得 a×Ai(1ip1) 也为模 p 意义下的完全剩余系,当然也是没有 0。因此 Aia×Ai 一一对应。

我们设 f=(p1)!,那么 fa×A1×a×A2××a×Ap1(modp),则 ap1×ff(modp),则 ap11(modp)

证毕。

定义

gcd(a,m)=1,则 aφ(m)1(modm)

证明:

与费马小定理类似。同样构造一个与 m 互质的序列。

r1,r2,,rφ(m) 为模 m 意义下的一个简化剩余系,gcd(a,m)=1a×r1,a×r2,,a×rφ(m) 也是模 m 意义下的一个简化剩余系。

因此 r1r2rφ(m))ar1×ar2××arφ(m)。两边同时约去 r1r2rφ(m)),得到 aφ(m)1(modm)

因此在满足 gcd(a,m)=1 时,aφ(m)1(modm)

证毕。

然后我们可以通过这个结论证明费马小定理。由于当 p 为素数时,φ(p)=p1,带入即可得到费马小定理。

扩展欧拉定理

定义

f(x)=ab{abmodφ(m)gcd(a,m)=1abgcd(a,m)1,bφ(m)a(bmodφ(m))+φ(m)gcd(a,m)1,bφ(m)(modm)

证明不会。

例题 P5091 【模板】扩展欧拉定理

一个很重要的步骤就是边输入边取模。

于荒林中独行夜路,就算心中志忑,也不必回头。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a,m,b,phi,flag;
inline int read()
{
int x = 0,f = 1;char ch = getchar();
while(ch < '0' or ch > '9'){
if (ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' and ch <= '9'){
x = x * 10 + ch - 48;
if(x >= phi) flag = 1;
x %= phi;ch = getchar();
}
return x * f;
}
int ksm(int a,int b){
int res = 1;
while(b){
if(b & 1) res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
int getPhi(int n){
int ans = n;
for(int i = 2;i * i <= n;i ++){
if(n % i == 0){
ans = ans / i * (i - 1);
while(n % i == 0) n /= i;
}
}
if(n > 1) ans = ans / n * (n - 1);
return ans;
}
signed main(){
scanf("%lld%lld",&a,&m);a %= m;
phi = getPhi(m);b = read();
b += (flag == 1 ? phi : 0);
printf("%lld",ksm(a,b));
return 0;
}

习题

1. P5221 Product

原式 =i=1Nj=1Ni×jgcd(i,j)2=(i=1Nj=1Ni×j)×(i=1Nj=1Ngcd(i,j))2=(N!)2N×(d=1Ndi=1Nj=1N[gcd(i,j)=d])2=(N!)2N×(d=1Ndi=1N/dj=1N/d[gcd(i,j)=d])

后者的指数我们可以使用欧拉函数的前缀和解决,但是空间 7.81MB 大概是能开 2N=106 大小的 int 数组,我们正常写有三个数组,一个是 vis 数组,是判断数是否是质数的数组,我们可以把它改成 bool 型数组;还有一个是 Prime 数组,是记录质数的,不过在 106 之内大概只有不到 8×104 个质数,因此就开这么大就行;还有一个是 phi 数组,显然我们是需要开 long long 的,但是开了 long long 空间就超了,因此我们用欧拉定理优化一下。由于这个模数是质数,因此我们直接让指数取模 φ(mod)=mod1 即可,这个时候我们就能开 int 数组存了。

若要终结枯灾,唯有九神巫同补天穹。
#include <bits/stdc++.h>
#define N 1000006
#define ll long long
#define MOD 104857601
using namespace std;
bool vis[N];
ll ans = 1,num = 1,sum;
int n,cnt,phi[N],Prime[80004];
void getPhi(){
vis[1] = 1,phi[1] = 1;
for(int i = 2;i <= n;i ++){
if(!vis[i]) Prime[++cnt] = i,phi[i] = i - 1;
for(int j = 1;j <= cnt and Prime[j] * i <= n;j ++){
vis[Prime[j] * i] = 1;
if(i % Prime[j] == 0){
phi[i * Prime[j]] = phi[i] * Prime[j];
break;
}
phi[i * Prime[j]] = phi[i] * (Prime[j] - 1);
}
}
for(int i = 2;i <= n;i ++) phi[i] = (phi[i] * 2 + phi[i - 1]) % (MOD - 1);
}
ll ksm(ll a,ll b){
ll res = 1;
while(b){
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
signed main(){
scanf("%d",&n);getPhi();
for(int i = 1;i <= n;i ++){
ans = ans * i % MOD;
num = num * ksm(i,phi[n / i]) % MOD;
}num = num * num % MOD;
sum = ksm(ans,2 * n) * ksm(num,MOD - 2) % MOD;
printf("%lld",sum);
return 0;
}
posted @   Joy_Dream_Glory  阅读(135)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起