数论之欧拉函数,费马小定理+欧拉定理+拓展欧拉定理

欧拉函数的基础

欧拉函数值的计算

定义:记φ(n)为<=n且和n互质的正整数的个数

公式:φ(n)= ∏ i = 1 n \prod_{i=1}^{n} i=1n ( 1 − 1 p i ) (1-\frac{1}{pi}) (1pi1)(其中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)=n1

欧拉函数常见计算例题

经典套路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]=[dn]×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=1nj=1n[gcd(i,j)=d]=i=1n/dj=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) dni=1n[gcd(i,n)=d]<=>dni=1n/d[gcd(i,n/d)=1]<=>dnφ(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 dnφ(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=1nj=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=1nj=1i[gcd(i,j)=d]=i=1n/dj=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=1nj=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=1nj=1n[gcd(i,j)=d]=i=1n/dj=1n/d[gcd(i,j)=1]=2i=1n/dj=1i[gcd(i,j)=1]1=2i=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=1nj=1n[gcd(i,j)=prime],n<=1e7。
  • 问题分析:
  • 枚举 d ∈ p r i m e d\in prime dprime,(问题就变成了例题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=1nj=1n[gcd(i,j)=prime]=dprime(2i=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=1nj=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=1ni=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)=dnφ(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;
}

欧拉函数建模构造例题

例题1

  • 例题描述: 已知一个 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=1nj=1n[gcd(i1,j1)=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=1n1j=1n1[gcd(i,j)=1]。即例题2.2。

费马小定理+欧拉定理+拓展欧拉定理

费马小定理:

  • 前提: a , p ∈ Z a,p \in Z a,pZ,p为质数, a ≢ 0 ( m o d p ) a\not\equiv 0 \pmod{p} a0(modp)
  • 定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod{p} ap11(modp) a b ≡ a b   m o d   ( p − 1 ) ( m o d p ) a^b\equiv a^{b\bmod (p-1)}\pmod {p} ababmod(p1)(modp)

欧拉定理:

  • 前提: a , p ∈ Z a,p\in Z a,pZ,且 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 ababmodφ(p)(modp)

【注意】:显然费马小定理只是欧拉定理的一个特殊情况。

扩展欧拉定理:

  • 前提: a , p ∈ Z a,p\in Z a,pZ
  • 定理: 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(n1),求 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(n1)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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值