欧拉函数的基础
欧拉函数值的计算
定义:记φ(n)为<=n且和n互质的正整数的个数
公式:φ(n)= ∏ i = 1 n \prod_{i=1}^{n} ∏i=1n ( 1 − 1 p i ) (1-\frac{1}{pi}) (1−pi1)(其中pi为n的质因子)
方法1:求一个数n的欧拉函数,质因数分解,O(sqrt(n))
long long eular(int n){
long long 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-1)/n;
//最后除的结果可能留有最后一个质数
return ans;
}
方法2:求1-n每个数的欧拉函数,类埃筛,O(nlogn)
- 若一个数 i 为质数,那么它的倍数的数 j 一定有 i 这个质因子
const int N=1e6+100;
long long E[N];
void init_eular(long long n) {
for(int i=1; i<=n; i++)E[i]=i;
for(int i=2; i<=n; i++) {
if(E[i]==i) {
for(int j=i; j<=n; j+=i) {
E[j]=E[j]/i*(i-1);
}
}
}
//当E[i]=i-1时,i为质数
}
方法3:求1-n每个数的欧拉函数,线性筛,O(n)
const int N=1e6+10,M=1e6;
long long prime[N],cnt,vis[N],phi[N],Sphi[N];
void init_phi() { //线性筛欧拉函数
Sphi[1]=phi[1]=1;
for(int i=2; i<=M; i++) {
if(vis[i]==0){
prime[++cnt]=i;
phi[i]=i-1;
}
for(int j=1; j<=cnt; j++) {
if(prime[j]*i>M)break;
vis[prime[j]*i]=1;
if(i%prime[j]==0) {
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[prime[j]]*phi[i];
}
}
for(int i=1; i<=M; ++i)Sphi[i]=Sphi[i-1]+phi[i];
}
欧拉函数的常见性质
- 当m与n互质时, φ ( m × n ) = φ ( n ) × φ ( m ) φ(m\times n) = φ(n)\times φ(m) φ(m×n)=φ(n)×φ(m)
- 当n为奇数, φ ( 2 n ) = 2 φ ( n ) φ(2n) = 2φ(n) φ(2n)=2φ(n)
- 当n为质数, φ ( n ) = n − 1 φ(n) = n-1 φ(n)=n−1
欧拉函数常见计算例题
经典套路1:
∑
i
=
1
n
[
g
c
d
(
n
,
i
)
=
d
]
=
[
d
∣
n
]
×
∑
i
=
1
n
/
d
[
g
c
d
(
n
/
d
,
i
)
=
1
]
\sum_{i=1}^n [gcd(n,i)=d]=[d|n]\times\sum_{i=1}^{n/d}[gcd(n/d,i)=1]
∑i=1n[gcd(n,i)=d]=[d∣n]×∑i=1n/d[gcd(n/d,i)=1]
经典套路2:
∑
i
=
1
n
∑
j
=
1
n
[
g
c
d
(
i
,
j
)
=
d
]
=
∑
i
=
1
n
/
d
∑
j
=
1
n
/
d
[
g
c
d
(
i
,
j
)
=
1
]
\sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=d]=\sum_{i=1}^{n/d}\sum_{j=1}^{n/d}[gcd(i,j)=1]
∑i=1n∑j=1n[gcd(i,j)=d]=∑i=1n/d∑j=1n/d[gcd(i,j)=1]
- 证明1:相当于枚举 d 的倍数,当 n/d 和倍数的最大公约数为1,其最大公约数为 d
- 证明2:相当于分别枚举 d 的倍数,当两个倍数的最大公约数为1,其最大公约数为 d
例题1:
- 题目描述: ∑ i = 1 n g c d ( i , n ) \sum_{i=1}^n gcd(i,n) ∑i=1ngcd(i,n)
- 问题分析:
- 枚举 d, ∑ d ∣ n ∑ i = 1 n [ g c d ( i , n ) = d ] < = > ∑ d ∣ n ∑ i = 1 n / d [ g c d ( i , n / d ) = 1 ] < = > ∑ d ∣ n φ ( n / d ) \sum_{d|n}\sum_{i=1}^n[gcd(i,n) = d ] <=> \sum_{d|n}\sum_{i=1}^{n/d}[gcd(i,n/d) = 1]<=> \sum_{d|n}φ(n/d) ∑d∣n∑i=1n[gcd(i,n)=d]<=>∑d∣n∑i=1n/d[gcd(i,n/d)=1]<=>∑d∣nφ(n/d)
- 则: ∑ i = 1 n g c d ( i , n ) \sum_{i=1}^n gcd(i,n) ∑i=1ngcd(i,n)= ∑ d ∣ n φ ( n / d ) × d \sum_{d|n} φ(n/d)\times d ∑d∣nφ(n/d)×d。
- 时间复杂度:枚举因子sqrt(n),对于每个因子,求 sqrt(n) 求欧拉函数,时间复杂度O(sqrt(n)x因子个数)
#include<bits/stdc++.h>
using namespace std;
long long E(long long n){
long long ans=n;
for(long long i=2;i*i<=n;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0)n=n/i;
}
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
int main(){
long long n;
cin>>n;
long long ans=0;
for(long long i=1;i*i<=n;i++){
if(n%i==0){
ans+=i*E(n/i);
if(i*i!=n)ans+=(n/i)*E(i);
}
}
cout<<ans;
return 0;
}
例题2.1:
- 题目描述: ∑ i = 1 n ∑ j = 1 i [ g c d ( i , j ) = d ] \sum_{i=1}^n \sum_{j=1}^i[gcd(i,j)=d] ∑i=1n∑j=1i[gcd(i,j)=d]
- 问题分析:
- ∑ i = 1 n ∑ j = 1 i [ g c d ( i , j ) = d ] = ∑ i = 1 n / d ∑ j = 1 i [ g c d ( i , j ) = 1 ] = ∑ i = 1 n / d φ ( i ) \sum_{i=1}^n\sum_{j=1}^i[gcd(i,j)=d]=\sum_{i=1}^{n/d}\sum_{j=1}^{i}[gcd(i,j)=1]=\sum_{i=1}^{n/d}φ(i) ∑i=1n∑j=1i[gcd(i,j)=d]=∑i=1n/d∑j=1i[gcd(i,j)=1]=∑i=1n/dφ(i)
例题2.2
- 题目描述: ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = d ] \sum_{i=1}^n \sum_{j=1}^n[gcd(i,j)=d] ∑i=1n∑j=1n[gcd(i,j)=d]
- 问题分析:
- ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = d ] = ∑ i = 1 n / d ∑ j = 1 n / d [ g c d ( i , j ) = 1 ] = 2 ∑ i = 1 n / d ∑ j = 1 i [ g c d ( i , j ) = 1 ] − 1 = 2 ∑ i = 1 n / d φ ( i ) − 1 \sum_{i=1}^n\sum_{j=1}^n[gcd(i,j)=d]=\sum_{i=1}^{n/d}\sum_{j=1}^{n/d}[gcd(i,j)=1]=2\sum_{i=1}^{n/d}\sum_{j=1}^{i}[gcd(i,j)=1]-1=2\sum_{i=1}^{n/d}φ(i)-1 ∑i=1n∑j=1n[gcd(i,j)=d]=∑i=1n/d∑j=1n/d[gcd(i,j)=1]=2∑i=1n/d∑j=1i[gcd(i,j)=1]−1=2∑i=1n/dφ(i)−1
例题2.3
- 题目描述: ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = p r i m e ] \sum_{i=1}^n\sum_{j=1}^n [gcd(i,j)=prime] ∑i=1n∑j=1n[gcd(i,j)=prime],n<=1e7。
- 问题分析:
- 枚举 d ∈ p r i m e d\in prime d∈prime,(问题就变成了例题2.2)
- 那么 ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = p r i m e ] = ∑ d ∈ p r i m e ( 2 ∑ i = 1 n / d φ ( i ) − 1 ) \sum_{i=1}^n\sum_{j=1}^n [gcd(i,j)=prime]=\sum_{d\in prime }(2\sum_{i=1}^{n/d}φ(i)-1) ∑i=1n∑j=1n[gcd(i,j)=prime]=∑d∈prime(2∑i=1n/dφ(i)−1)
- 时间复杂度:先预处理出欧拉函数,然后筛出 prime,枚举 d ,累加答案。O(n)
例题2.4:
- 题目描述: ∑ i = 1 n ∑ j = 1 i g c d ( i , j ) \sum_{i=1}^n \sum_{j=1}^{i} gcd(i,j) ∑i=1n∑j=1igcd(i,j)
- 问题分析:
- 枚举 d,问题就变成了例题2.1,
- 原式 = ∑ d = 1 n ∑ i = 1 n φ ( i / d ) × d \sum_{d=1}^{n} \sum_{i=1}^{n}φ(i/d)\times d ∑d=1n∑i=1nφ(i/d)×d
例题3:
- 题目描述: 已知n,求 ∑ i = 1 n n g c d ( n , i ) \sum_{i=1}^n n^{gcd(n,i)} ∑i=1nngcd(n,i)
- 问题分析:
- 枚举 d, ∑ i = 1 n [ g c d ( n , i ) = d ] < = > ∑ i = 1 n / d [ g c d ( n / d , i ) = 1 ] < = > φ ( n / d ) \sum_{i=1}^n[gcd(n,i)=d]<=>\sum_{i=1}^{n/d}[gcd(n/d,i)=1] <=>\varphi(n/d) ∑i=1n[gcd(n,i)=d]<=>∑i=1n/d[gcd(n/d,i)=1]<=>φ(n/d),
- 那么 ∑ i = 1 n n g c d ( n , i ) = ∑ d ∣ n φ ( n / d ) × n d \sum_{i=1}^n n^{gcd(n,i)}=\sum_{d|n} \varphi(n/d)\times n^{d} ∑i=1nngcd(n,i)=∑d∣nφ(n/d)×nd
long long mod=1e9+7;
long long E(long long n) {
long long ans=n;
for(long long i=2; i*i<=n; i++) {
if(n%i==0) {
ans=ans/i*(i-1);
while(n%i==0)n=n/i;
}
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
long long ksm(long long a,long long b) {
long long ans=1;
while(b) {
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b=b/2;
}
return ans;
}
int main() {
long long n,ans=0;
cin>>n;
for(long long i=1; i*i<=n; i++) {
if(n%i==0) {
ans=(ans+E(n/i)*ksm(n,i))%mod;
if(i*i!=n)ans=(ans+E(i)*ksm(n,n/i))%mod;
}
}
cout<<ans*ksm(n,mod-2)%mod;
return 0;
}
欧拉函数建模构造例题
- 例题描述: 已知一个 n × n n\times n n×n 的方阵 ,n2 个人整整齐齐的站在方阵里。你站在方阵的左下角 (1,1),请问你能看到多少人。
- 问题分析: 考虑 (1,1) 是否能看到 (i,j) ,若 gcd(i-1,j-1)=1,则可以看到,否则不能。那么问题就转化成了求 ∑ i = 1 n ∑ j = 1 n [ g c d ( i − 1 , j − 1 ) = 1 ] \sum_{i=1}^n\sum_{j=1}^n [gcd(i-1,j-1)=1] ∑i=1n∑j=1n[gcd(i−1,j−1)=1],我们把第 1 行和第 1 列单独考虑,那么问题就变成了 2 + ∑ i = 1 n − 1 ∑ j = 1 n − 1 [ g c d ( i , j ) = 1 ] 2+\sum_{i=1}^{n-1}\sum_{j=1}^{n-1} [gcd(i,j)=1] 2+∑i=1n−1∑j=1n−1[gcd(i,j)=1]。即例题2.2。
费马小定理+欧拉定理+拓展欧拉定理
费马小定理:
- 前提: a , p ∈ Z a,p \in Z a,p∈Z,p为质数, a ≢ 0 ( m o d p ) a\not\equiv 0 \pmod{p} a≡0(modp)
- 定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod{p} ap−1≡1(modp), a b ≡ a b m o d ( p − 1 ) ( m o d p ) a^b\equiv a^{b\bmod (p-1)}\pmod {p} ab≡abmod(p−1)(modp)
欧拉定理:
- 前提: a , p ∈ Z a,p\in Z a,p∈Z,且 gcd ( a , p ) = 1 \gcd(a,p)=1 gcd(a,p)=1
- 定理: a φ ( p ) ≡ 1 ( m o d p ) a^{\varphi(p)}\equiv 1\pmod{p} aφ(p)≡1(modp), a b ≡ a b m o d φ ( p ) ( m o d p ) a^b\equiv a^{b\bmod \varphi(p)}\pmod p ab≡abmodφ(p)(modp)。
【注意】:显然费马小定理只是欧拉定理的一个特殊情况。
扩展欧拉定理:
- 前提: a , p ∈ Z a,p\in Z a,p∈Z
- 定理: a b ≡ { a b , b < φ ( p ) a b m o d φ ( p ) + φ ( p ) , b ≥ φ ( p ) ( m o d p ) a^b\equiv\left\{\begin{matrix}a^b,b<\varphi(p)\\a^{b\bmod\varphi(p)+\varphi(p)},b\ge\varphi(p)\end{matrix}\right.\pmod p ab≡{ab,b<φ(p)abmodφ(p)+φ(p),b≥φ(p)(modp)
【注意】:二者条件严格,当 b < φ ( p ) b<\varphi(p) b<φ(p)时,不可以用下面的式子。
拓展欧拉定理降幂例题
模板题:单次降幂
- 题目描述: 给定 a,b,p,求 a b % p a^b\%p ab%p,其中 b < = 1 0 100000 b<=10^{100000} b<=10100000。
- 问题分析: 无任何限制,根据拓展欧拉定理降一次幂,然后快速幂即可。
#include<bits/stdc++.h>
using namespace std;
long long E(long long n){
long long ans=n;
for(long long i=2;i*i<=n;i++){
if(n%i==0){
ans=ans/i*(i-1);
while(n%i==0)n=n/i;
}
}
if(n>1)ans=ans/n*(n-1);
return ans;
}
long long ksm(long long a,long long b,long long mod){
long long ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b=b/2;
}
return ans;
}
int main(){
long long a,m;
cin>>a>>m;
long long t=E(m);
string s;
long long b=0,flag=0;
cin>>s;
for(int i=0;i<s.size();i++){
b=b*10+s[i]-'0';
if(b>=t){
b=b%t;
flag=1;
}
}
if(flag)b+=t;
cout<<ksm(a,b,m);
return 0;
}
以下为多次降幂,形如:
a
l
a
l
+
1
a
l
+
2
a
r
(
m
o
d
p
)
a_{l}^{a_{l+1}^{a_{l+2}^{ar}}} \pmod p
alal+1al+2ar(modp),看似递归不断,实则对于每层递归使用拓展欧拉定理降一次幂,使得 p 的不断减小,当 p = 1时,递归到达尽头。而根据欧拉函数不断求p,logn级下,p 就会到达 1。即对于一个数,不断取幂,事实上只有前 log 次有效。
【注意】:拓展欧拉定理是有两个分支的。
例题2
- 题目描述: 已知 f ( 1 ) = 1 , f ( n ) = 2 f ( n − 1 ) f(1)=1,f(n)=2^{f(n-1)} f(1)=1,f(n)=2f(n−1),求 f ( n ) ( m o d p ) f(n)\pmod p f(n)(modp),其中 p<=1e7,n 无穷大。
- 问题分析: 根据拓展欧拉定理,由于 f ( n ) f(n) f(n)无穷大,则 2 f ( n ) ≡ 2 f ( n − 1 ) m o d φ ( p ) + φ ( p ) ( m o d p ) 2^{f(n)}\equiv2^{f(n-1)\bmod\varphi(p)+\varphi(p)} \pmod p 2f(n)≡2f(n−1)modφ(p)+φ(p)(modp),我们可以不断递归去求 2 f ( n ) 2^{f(n)} 2f(n),由于 p 最后会等于1,此时 2 f ( n ) ( m o d p ) = 0 2^{f(n)}\pmod p=0 2f(n)(modp)=0。
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10,M=1e7;
long long phi[N],prime[N],cnt,vis[N];
void init_phi() { //线性筛欧拉函数
phi[1]=1;
cnt=0;
for(int i=2; i<=M; i++) {
if(vis[i]==0){
prime[++cnt]=i;
phi[i]=i-1;
}
for(int j=1; j<=cnt; j++) {
if(prime[j]*i>M)break;
vis[prime[j]*i]=1;
if(i%prime[j]==0) {
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[prime[j]]*phi[i];
}
}
}
long long ksm(long long a,long long b,long long mod){
long long ans=1;
while(b){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
b=b/2;
}
return ans;
}
long long f(long long p){
if(p==1)return 0;
else return ksm(2,f(phi[p])%phi[p]+phi[p],p);
}
int main() {
long long T,p;
cin>>T;
init_phi();
while(T--){
cin>>p;
cout<<f(p)<<endl;
}
return 0;
}
例题3:
- 题目描述: 给定 n 个数 ai,和模数 p 。给定 q 个询问,每次询问给定 l,r,求 a l a l + 1 a l + 2 a r ( m o d p ) a_{l}^{a_{l+1}^{a_{l+2}^{ar}}} \pmod p alal+1al+2ar(modp),n,q<=1e5,ai,p<=1e9
- 问题分析: 对于每次查询看似是 O(n) 的暴力,实则通过拓展欧拉定理降幂,p很快达到 1,直接递归即可。但是幂不确定是否比 φ ( p ) \varphi(p) φ(p)大,所以还需判断。考虑降取模重定义成下面这个式子:这样就可以适用于两种情况的任意一种了。
long long Mo(long long x,long long p) {
return x<p?x:(x%p+p);
}
- 由于 p<=1e9,算欧拉函数预处理是不行了,考虑欧拉函数计算的次数并不多,选择暴力+记忆化map存算过的值。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long a[N];
unordered_map<long long,long long>phi;
inline long long read(){
register long long x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
long long Count_phi(long long n) {
if(phi[n])return phi[n];
long long id=n,ans=n;
for(long long i=2; i*i<=n; i++) {
if(n%i==0) {
ans=ans/i*(i-1);
while(n%i==0)n=n/i;
}
}
if(n>1)ans=ans/n*(n-1);
return phi[id]=ans;
}
long long Mo(long long x,long long p) {
return x<p?x:(x%p+p);
}
long long ksm(long long a,long long b,long long mod) {
long long ans=1;
while(b) {
if(b&1)ans=Mo(ans*a,mod);
a=Mo(a*a,mod);
b=b>>1;
}
return ans;
}
long long f(long long l,long long r,long long p) {
if(p==1)return 1;
if(l==r) return Mo(a[l],p);
else return ksm(a[l],f(l+1,r,Count_phi(p)),p);
}
int main() {
long long n,p,q,l,r;
n=read();
p=read();
for(int i=1; i<=n; i++)a[i]=read();
q=read();
for(int i=1; i<=q; i++) {
l=read(),r=read();
printf("%lld\n",f(l,r,p)%p);
}
return 0;
}
例题4:
- 题目描述: 给定 n,c,以及 n 个数 ai 。q 次操作,每次操作修改区间 [l,r] 上的每个点的值 a i a_i ai 为 c a i c^{a_i} cai 或查询区间 [l,r] 的和 ( m o d p ) \pmod p (modp) 。n,q<=5e4,0<c<p<=1e8,0<=ai<p
- 问题分析: 对于一个点,修改若干次后变成: c c c a i c^{c^{c^{ai}}} cccai,而根据拓展欧拉定理,只有前 logp 次修改时有效的。可以先预处理出来每个点的log次修改后的值。再在线段树上区间修改,同时维护区间最少修改次数,若区间最少修改次数 >= 所有点最大修改次数,那么不必再去修改该区间内的所有点。
【还没写完】
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
struct node{
long long l,r,sum,minx;
}tree[N];
long long cnt,a[N];
void pushup(long long now){
tree[now].sum=tree[tree[now].l].sum+tree[tree[now].r].sum;
tree[now].minx=min(tree[tree[now].l].minx,tree[tree[now].r].minx);
}
long long build(long long now,long long l,long long r){
now=++cnt;
if(l==r){
tree[now].sum=a[l];
tree[now].minx=1;
return now;
}
long long mid=(l+r)/2;
tree[now].l=build(tree[now].l,l,mid);
tree[now].r=build(tree[now].r,mid+1,r);
pushup(now);
return now;
}
void update(long long now,long long ql,long long qr){
if(tree[now].minx>=maxp)return;
long long l=tree[now].l,r=tree[now].r;
if(l==r){
tree[now].minx++;
tree[now].sum=dfs(a[l],tree[now].minx);
return;
}
long long mid=(l+r)/2;
if(ql<=mid)update(tree[now].l,ql,qr);
if(qr>mid)update(tree[now].r,ql,qr);
pushup(now);
}
int main(){
long long n,q,p,c,op,root=0;
cin>>n>>q>>p>>c;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
root=build(root,1,n);
for(int i=1;i<=q;i++){
scanf("%lld%lld%lld",&op,&l,&r);
if(op==0)update(root,l,r);
else printf("%lld\n",query(root,l,r));
}
return 0;
}