欧拉函数&乘法逆元

 

一、欧拉函数简单应用

·定义:

对于一个正整数n,它的欧拉函数的值即{p|p∈[1,n),p∈N+,p与n互质}的集合的大小,我们用φ(n)表示正整数n的欧拉函数

φ(n)=n(1-1/p1)(1-1/p2)……(1-1/pk),其中p1~pk为n的质因数

附证明:https://blog.csdn.net/paxhujing/article/details/51353672

·性质:

1)如果n是素数,那么φ(n)=n-1,逆向也成立

2)如果n是素数,k是正整数,那么φ(n^k)=(n-1)*n^(k-1)

证明:显然一共有n^k-1个正整数,其中能被n^k整除的有n*t,t∈{1,2……n^(k-1)-1},所以有[(n^k)-1]-[n^(k-1)-1]个

3)如果m,n是互质的正整数,那么φ(mn)=φ(m)φ(n)

证明:m,n没有公共约数,我们令p1~pk1表示m的质因数,q1~qk2表示n的质因数,我们由计算公式可得:

φ(m)φ(n)=m(1-1/p1)(1-1/p2)……(1-1/pk1)n(1-1/q1)(1-1/q2)……(1-1/qk2)

φ(mn)=mn(1-1/p1)(1-1/p2)……(1-1/pk1)(1-1/q1)(1-1/q2)……(1-1/qk2)

4)n=p1^a1*p2^a2*……*pk^ak为n的素数幂分解(根据唯一分解定理),那么φ(n)=n(1-1/p1)(1-1/p2)……(1-1/pk)

证明:你们一定行!

由性质3:

φ(n)=φ(p1^a1)φ(p2^a2)……φ(pk^ak)

=(p1-1)*p1^(a1-1)……(pk-1)*pk^(ak-1)

=p1^a1*……*pk^ak*(1-1/p1)*……*(1-1/pk)

5)一个数n的质因数的和=φ(n)*n/2

 

·练习:

#oj3623 Relatives

题目大意:求φ(n)

模板题,不难想到利用性质4来求φ(n),显然我们需要进行质因数分解,代码:

#include
#define int long long
using namespace std;
int n;
int euler1(int m){//直接求 
	int ans=m;
	for(int i=2;i<=m;i++){
		if(m%i==0){
//			ans=ans*(1-1/i)=>
//			ans=ans-ans/i=>
			ans=ans/i*(i-1);//防止溢出 
			while(m%i==0)m/=i;
		}
	}return ans;
}
int euler2(int m){//O(√n)
	int ans=m;
	for(int i=2;i*i<=m;i++){//任意一个合数都有不大于根号n的约数 
		if(m%i==0){
			ans=ans/i*(i-1);
			while(m%i==0)m/=i;
		}
	}if(m>1)ans=ans/m*(m-1);//最后的pk 
	return ans;
}
#define MAXN 100001
int euler[MAXN];
int euler3(int m){//筛法求euler函数O(n),假的,千万级就跑得很慢了 
	for(int i=1;i<=m;i++)euler[i]=i;
	for(int i=2;i<=m;i++)if(euler[i]==i){//利用性质1 
		for(int j=i;j<=m;j+=i)euler[j]=euler[j]/i*(i-1);
	}return euler[m];
}
main(){
	while(~scanf("%lld",&n)&&n)cout<

 

#oj3624 原根

 

题目大意:给定奇素数,求它的原根

原根是个数学符号,它的定理:n的原根为φ(φ(n)),当结论背下来吧,感兴趣的同学可以去网上找这方面的资料

 

#oj2904 MMT数

题目大意:给定一个数n,需要我们求n与x不互质且x不为n的约数的个数(x<=n)

发现正面求解不太好搞,我们考虑逆向求解

对于第一个条件(求互质):ans1=n-φ(n)

对于第二个条件(求约数个数):我们由素数幂分解得到ans2=(a1+1)(a2+1)……(ak+1)个(详见性质4)

于是我们就得到了最终答案:ans=n-ans1-ans2个?

注意1又是n的约数且含在了φ(n)中,所以算了两次,因此我们要+1

#include
using namespace std;
#define MAXN 100001
int n,q,ans1,ans2,cnt[MAXN];
void euler(int m){
	ans1=m,ans2=1;
	for(int i=2;i*i<=m;i++){
		if(m%i==0){
			ans1=ans1/i*(i-1);
			++q;
			while(m%i==0)m/=i,++cnt[q];//求ak的大小
		}
	}
	for(int i=1;i<=q;i++)ans2*=(cnt[i]+1);//+1?0次方,即不要它自己 
	if(m>1)ans1=ans1/m*(m-1),ans2*=2;//因为我们是取的根号,还有一半的约数个数没有取到 
}
int main(){
	scanf("%d",&n);
	euler(n);
	cout<

 

#oj2602 Farey Sequence

 

题目大意:给定一个数n,2~n的欧拉函数之和

去水题吧,注意要开longlong,而且数据貌似改了,不用特判n=1的

 

#oj2827 仪仗队

题目大意:见题面(有点长哇)

 

#oj2612 最大公约数

题目大意:优化如下代码

Int sum=0;

For(i,1,n-1)

For(j,i+1,n)sum+=gcd(i,j)

多组数据Case<=2e4,n<=1e6

对于一组Gcd(i,n),我们这样考虑:

Gcd(i*pi,n)=pi即Gcd(i,n/pj)=1,而我们可以求出满足Gcd(i,n/pj)=1的i有φ(n/pj)个,所以Gcd(i*pi,n)=pi也有φ(n/pj)个,那么对于一个单独n:(n)+=φ(n/pi)*pi,pi为n的因数

而对于此题,我们只需递推出所有状态就好了

#include
#define int long long
using namespace std;
#define MAXN 1000001
int r,n,q[MAXN],euler[MAXN];
void init(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",q+i);
	for(int i=1;i<=n;i++)r=max(r,q[i]);
}
void calc(){
	for(int i=1;i<=r;i++)euler[i]=i;
	for(int i=2;i<=r;i++)if(euler[i]==i){
		for(int j=i;j<=r;j+=i)euler[j]=euler[j]/i*(i-1);
	}
}
int f[MAXN];
void solv(){//对于每一个询问n+=euler[pi]*pi
	for(int pi=1;pi<=r;pi++)//公约数大小 
		for(int n_pi=2;n_pi<=r/pi;n_pi++)f[pi*n_pi]+=euler[n_pi]*pi;//注意n/pi不能为本身,因为没有意义
		//gcd(x,n)=pi
		//gcd(x/pi,n/pi)=1 
	for(int i=1;i<=r;i++)f[i]+=f[i-1];
	for(int i=1;i<=n;i++)cout<

 

#oj3224 longge的问题

题目大意:给定一个整数n,求sum gcd(i,n)(1<=i<=n)

上一道题都成功解决,这一道题就不说了,只是注意n∈(0,2^32),因此不能开数组,而应该实时计算(见第二种写法)

#oj2819 沙拉公主的困惑(逆元)

#oj4780 奇数国(逆元)

#oj1316 机器人M号(逆元)

 

二、乘法逆元

·定义:

对于整数a,p,若存在一个整数x,使ax≡1 (mod p)ax%p=1成立,则称x是a对p的逆元(当且仅当gcd(a,p)=1时,才存在且存在唯一逆元),我们用inv(a)表示x

·应用:

模意义下除法不能直接操作,因此要引入逆元(还有加法逆元等,一般指乘法逆元)

·求法:

·费马小定理:若p是质数且gcd(a,p)=1,则a^(p-1)≡1 (mod p)

显然我们有a*a^(p-2)≡1 (mod p),即inv(a)=a^(p-2),用快速幂计算inv(a),复杂度log2(a),当模数p是质数时,用费马求解

·欧拉定理:若gcd(a,p)=1,则a^(φ(p))≡1(mod p)

显然此时inv(a)=a^(φ(p)-1),使用欧拉函数+快速幂计算,复杂度同费马,但较费马而言,模数p可以不为质数,因此应用范围广一点

·扩展欧几里得:若gcd(a,b)=1,则ax+by=1

这个不定方程的解x就是a对b的逆元,y就是b对a的逆元

证明:显然ax%b+by%b=1%b=>ax%b=(1%b)=1=>ax≡1 (mod b)

·线性递推:当p是质数的时候,inv(a)=(p-p/a)*inv(p%a)%p

证明:设x=p%a,y=p/a,则有:

x+ay=p=>

(x+ay)%p=0=>

x*inv(a)%p=(p-y)%p=>

inv(a)=(p-y)*inv(x)%p=>

inv(a)=(p-p/a)*inv(p%a)%p

·练习:

#5208乘法逆元(模板题)

#include
#define int long long
using namespace std;
int n,p;
struct FEIMA{
	inline int pow_mod(int a,int b,int p){//a的b次方求余p快速幂 
    	int ret=1;
    	while(b){
       		if(b&1)ret=(ret*a)%p;
        	a=(a*a)%p;
        	b>>=1;
    	}return ret;
	}
	inline int FeiMaX(int a,int p){//费马求a关于b的逆元 
    	return pow_mod(a,p-2,p);
	}
	inline void solv(){
		for(int i=1;i<=n;i++)cout<>=1;
    	}return ret;
	}
	inline int OuLa(int a,int p){
		return pow_mod(a,Eulerp-1,p);
	}
	inline int euler(int m){
		int ans=m;
		for(int i=2;i*i<=m;i++){
			if(m%i==0){
				ans=ans/i*(i-1);
				while(m%i==0)m/=i;
			}
		}if(m>1)ans=ans/m*(m-1);
		return ans;
	}
	inline void solv(){
		Eulerp=euler(p);
		for(int i=1;i<=n;i++)cout<

 

学了乘法逆元,我们就能ac前面的几道题了!

 

#oj2819 沙拉公主的困惑

题目大意:给定正整数n,m,p,求(0,n!]中所有与m!互质的数的个数%p(数据默认n>m)

我们还是先研究局部(因为n!必然能够被m!整除),对于一个区间m!,如果找到了一个数p与m!互质,那么,p+m!,p+2*m!,……,p+(n!/m!-1)*m!都与p是相同性质的,因此我们的答案即ans(m!)*(n!/m!)即φ(m!)*(n!/m!),我们将它展开得到:n!*(1-1/p1)……(1-1/pk)=>n!*[(p1-1)/p1]……[(pk-1)/pk],又因为n,m<=10000000而且内存限制足够大,暴力预处理即可

/* 
暴力处理n!:n数组
处理1-1/pi:inv数组(逆元),key数组(表示(1-1/p1)……(1-1/pk))
对于每个key[m],我们这样考虑:
当m为素数时,显然key[m]=key[m-1]*(1-1/m)
当m不为素数,我的m肯定能被分成若干小于m的合数积,因此key[m]=key[m-1] 
*/
#include
using namespace std;
#define N 10000000
#define int long long
int cas,p;
int key[N+10];
int inv[N+10];
int cnt,prime[N+10];
bool vst[N+10];
inline void prepP(){//欧拉素数筛 
	for(int i=2;i<=N;i++){
		if(!vst[i])prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++){
			vst[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}
}
inline void prepVK(){
	inv[1]=1;
	for(int i=2;i<=N;i++)inv[i]=(p-p/i)*inv[p%i]%p;//逆元 
	key[1]=1;
	for(int i=2;i<=N;i++){
		key[i]=key[i-1];//
		if(!vst[i])key[i]=key[i]*(i-1)%p*inv[i]%p;
	}
}
int n[N+10];
inline void prepN(){
	n[1]=1;
	for(int i=2;i<=N;i++)n[i]=n[i-1]*i%p;
}
main(){
	scanf("%lld%lld",&cas,&p);
	prepP();
	prepVK();
	prepN();
	while(cas--){
		int x,y;scanf("%lld%lld",&x,&y);
		printf("%lld\n",n[x]*key[y]%p);
	}
}

 

 

 

#oj4780 奇数国(线段树+欧拉+逆元)

题目大意:看题目……

好题,我们很容易想到答案即φ(TTr/TT/(l-1)),即TT(l~r)*(1-1/p1)*(1-1/p2)……(1-1/pk),我们又注意到总共只有60个质因数……

#include
#define ll long long
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
#define N 100000
#define p 19961993
#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
bool vst[287];
int inv[287],prime[67];
struct Seg{
	ll c[N*4];
	bitset<61>s[N*4];
	#define ls v<<1
	#define rs v<<1|1
	inline void cull(int v,int k){
		c[v]=k;
		s[v]=0;
		FOR(i,1,60)if(k%prime[i]==0)s[v][i]=1;
	}
	inline void maintain(int v){
		c[v]=c[ls]*c[rs]%p;
		s[v]=s[ls]|s[rs];
	}
	inline void Modify(int v,int L,int r,int x,int k){
		if(L>x||r>1;
		Modify(ls,L,Mid,x,k),Modify(rs,Mid+1,r,x,k);
		maintain(v);
	}
	inline pair > query(int v,int L,int r,int a,int b){
		if(L>b||r>1;
		pair >Left=query(ls,L,Mid,a,b);
		pair >Right=query(rs,Mid+1,r,a,b);
		return make_pair(Left.first*Right.first%p,Left.second|Right.second);
	}
	inline void build(int v,int L,int r){
		if(L==r)return cull(v,3),void();
		int Mid=L+r>>1;
		build(ls,L,Mid),build(rs,Mid+1,r);
		maintain(v);
	}
}t;
inline void prep(){
	FOR(i,2,281){
		if(!vst[i])prime[++prime[0]]=i;
		for(int j=1;j<=prime[0]&&i*prime[j]<=281;j++){
			vst[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}inv[1]=1;
	FOR(i,2,281)inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}
inline void GetAns(int a,int b){
	pair >ans=t.query(1,1,N,a,b);
	FOR(i,1,60)if(ans.second[i])ans.first=(ll)ans.first*(prime[i]-1)*inv[prime[i]]%p;
	cout<
using namespace std;
#define N 100000
#define p 19961993
#define int long long
#define FOR(i,L,R) for(register int i=(L);i<=(R);i++)
bool vst[290];
int prime[61],inv[290];
int BIT[61]={1};
inline void prep(){
	FOR(i,1,60)BIT[i]=BIT[i-1]<<1;
	FOR(i,2,281){
		if(!vst[i])prime[++prime[0]]=i;
		for(int j=1;j<=prime[0]&&i*prime[j]<=281;j++){
			vst[i*prime[j]]=1;
			if(i%prime[j]==0)break;
		}
	}inv[1]=1;
	FOR(i,2,281)inv[i]=(p-p/i)*inv[p%i]%p;
//	int ans=0;FOR(i,1,61)ans+=BIT[i];cout<b||r>1;
		return f?query(f,v<<1,L,Mid,a,b)|query(f,v<<1|1,Mid+1,r,a,b):query(f,v<<1,L,Mid,a,b)*query(f,v<<1|1,Mid+1,r,a,b)%p;
	}
	inline void Modify(int f,int v,int L,int r,int x,int k){
		if(L>x||r>1;
		Modify(f,v<<1,L,Mid,x,k),Modify(f,v<<1|1,Mid+1,r,x,k);
		maintain(f,v);
	}
	inline void build(int f,int v,int L,int r){
		if(L==r)return cull(f,v,3),void();
		int Mid=L+r>>1;
		build(f,v<<1,L,Mid);
		build(f,v<<1|1,Mid+1,r);
		maintain(f,v);
	}
}t;
inline void GetAns(int a,int b){
	int ans1=t.query(0,1,1,N,a,b),ans2=t.query(1,1,1,N,a,b);
	for(int i=1;i<=60;i++)
		if(ans2&BIT[i])ans1=ans1*(prime[i]-1)*inv[prime[i]]%p;
	cout<

 

 

#oj1316 机器人M号(dp+欧逆)

题目大意:见题面……

思路?代码?我不会……

 

 

 

你可能感兴趣的:(数学)