zoukankan      html  css  js  c++  java
  • 欧拉函数(汇总&例题)

    定义

    欧拉函数 $varphi(n)$表示小于等于$n$的正整数中与$n$互质的数的数目。

    性质

    1、积性函数(证明)。

    2、$varphi(1)=1$(显然)

    3、对于质数$n$,$varphi(n)=n-1$(显然)

    4、对于质数的幂$n=p^k$(其中$p$为质数,$k$为正整数),$varphi(n)=p^{k-1}cdot(p-1)$

    证明:

    归纳法,在$k=1$时显然成立,假设当$k$为$k-1$时成立,那么对于将$1,2,...p^k$中每一个数表示为$xcdot p^{k-1}+d$,其中$0leq x<p$,$1leq dleq p^{k-1}$,若某一个数对$varphi(n=p^k)$有贡献,则其$d$的部分一定不含质因子$p$,因而一定对$varphi(p^{k-1})$有贡献,所以,恰好每一个对$varphi(p^{k-1})$有贡献的数都会对$varphi(p^k)$有$p$次贡献,所以有$varphi(p^k)= varphi(p^{k-1}) imes p=p^{k-2} imes (p-1) imes p=p^{k-1} imes (p-1)$,得证。

    计算

    不妨设$n=prod p_i^{t_i}$,其中$p_i$是质数,$t_i$为正整数。

    则有$varphi(n)=n prod frac {p_i-1} {p_i}$。

    特别的,$varphi(1)=1$。

    证明的话,利用积性函数的性质和性质四组合即可证明。

    反演

    利用欧拉函数本身定义和其一条重要性质$$n=sumlimits_{d|n} varphi(d)$$

    (其证明涉及到了我的知识盲区)

    在莫比乌斯反演中,我们常利用莫比乌斯函数的性质把$$sumlimits_{x=1}^{n} sumlimits_{y=1}^{n} [gcd(x,y)=1]$$ 

    转化为$$sumlimits_{x=1}^n sumlimits_{y=1}^n sumlimits_{d|x,d|y} mu(d)$$

    然后进一步改变枚举项

    那么类似的,我们也可以利用欧拉函数的定义将其转化$$sumlimits_{x=1}^{n} sumlimits_{y=1}^{n} [gcd(x,y)=1]Rightarrow 2( sumlimits_{i=1}^{n} varphi(i))-1$$

     

    这里两道利用欧拉函数进行反演的例题

    1、BZOJ4804 欧拉心算

    $T$组数据,给定$n$求$sumlimits_{x=1}^{n} sumlimits_{y=1}^{n} varphi(gcd(x,y))$,$(Tleq 5000,nleq 10^7)$

    和上文的方法类似

    $$sumlimits_{x=1}^{n} sumlimits_{y=1}^{n} varphi(gcd(x,y))$$

    $$Downarrow$$

    $$sumlimits_{d=1}^{n} varphi(d)sumlimits_{x=1}^{lfloor frac nd floor}sumlimits_{y=1}^{lfloor frac nd floor}[gcd(x,y)=1]$$

    $$Downarrow$$

    $$sumlimits_{d=1}^{n} varphi(d)(2( sumlimits_{i=1}^{lfloor frac nd floor} varphi(i))-1)$$这样只需要对前半部分数论分块,对后半部分线性筛预处理前缀和即可。

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define M 10000010
    using namespace std;
    int read(){
    	int nm=0,fh=1;char cw=getchar();
    	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
    	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
    	return nm*fh;
    }
    int n,m,p[M],ph[M],tot,T;
    LL G[M];
    bool vis[M];
    void init(){
    	memset(vis,true,sizeof(vis));
    	ph[1]=G[1]=1,vis[1]=false;
    	for(int i=2;i<M;i++){
    		if(vis[i]) ph[i]=i-1,p[++tot]=i;
    		for(int j=1;j<=tot&&p[j]*i<M;j++){
    			int num=p[j]*i; vis[num]=false;
    			if(i%p[j]==0){ph[num]=ph[i]*p[j];break;}
    			ph[num]=ph[i]*ph[p[j]];
    		}
    		G[i]=G[i-1]+ph[i];
    	}
    }
    LL calc(){
    	int now=1,RS,D;
    	LL ans=0;
    	while(now<=n){
    		D=n/now,RS=n/D;
    		ans+=(G[RS]-G[now-1])*((G[D]<<1)-1);
    		now=RS+1;
    	}
    	return ans;
    }
    void write(LL x){
    	if(x>9) write(x/10);
    	putchar(x%10+'0'); 
    }
    int main(){
    	init(),T=read();
    	while(T--) n=read(),write(calc()),putchar('
    ');
    	return 0;
    }

    2、BZOJ3518 点组计数(权限题)

    求$n imes m$方格点阵上任选三点共线的方案数(不同顺序属于同种方案),$n,mleq 10^5$

    将水平和竖直的直线单独取出计算

    对于倾斜的直线,枚举共线的三点的两端的横坐标只差$x$与纵坐标之差$y$。

    他们中间的方格点的数量就应该是$gcd(x,y)-1$,接着再把$-1$单独取出计算,再利用$n=sum_{d|n} varphi(d)$这一条性质交换枚举项进行计算即可。$$ans1=ncdot C_m^3 + mcdot C_n^3$$

    $$ans2=2sumlimits_{i=1}^nsumlimits_{j=1}^m(gcd(i,j)-1)(n-i)(m-j)$$

    $$ans2=2(t1-t2),t1=sumlimits_{i=1}^nsumlimits_{j=1}^m gcd(i,j)(n-i)(m-j),t2=frac{n(n-1)}{2}cdot frac{m(m-1)}{2}$$

    $$n=sumlimits_{d|n} varphi(d)$$

    $$Downarrow$$

    $$sumlimits_{i=1}^nsumlimits_{j=1}^mgcd(i,j)=sumlimits_{i=1}^nsumlimits_{j=1}^msumlimits_{d|i,d|j}varphi(d)=sumlimits_{d=1}^{min(n,m)}varphi(d) lfloor frac nd floor lfloor frac md floor$$

    $$Downarrow$$

    $$t1=sumlimits_{d=1}^nvarphi(d)sumlimits_{i=1}^{lfloor frac n d floor}(n-i imes d)sumlimits_{j=1}^{lfloor frac m d floor} (m-j imes d)$$仍然只需要常规的数论分块求解即可。

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define M 50020
    #define mod 1000000007
    using namespace std;
    LL read(){
    	LL nm=0,fh=1;char cw=getchar();
    	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
    	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
    	return nm*fh;
    }
    LL n,m,p[M],ph[M],tot,ans,MAXN;
    bool vis[M];
    void init(){
    	memset(vis,true,sizeof(vis));
    	ph[1]=1,vis[1]=false,MAXN=min(n,m);
    	for(LL i=2;i<=max(n,m);i++){
    		if(vis[i]) ph[i]=i-1,p[++tot]=i;
    		for(LL j=1;j<=tot&&p[j]*i<MAXN;j++){
    			LL num=p[j]*i; vis[num]=false;
    			if(i%p[j]==0){ph[num]=ph[i]*p[j];break;}
    			ph[num]=ph[i]*ph[p[j]];
    		}
    	}
    }
    LL ari(LL fs,LL ed,LL tot){return ((fs+ed)*(tot)/2)%mod;}
    LL calc(){
    	LL res=0;
    	for(LL d=1;d<MAXN;d++){
    		LL t1=ari(n-d,n-((n-1)/d)*d,(n-1)/d);
    		LL t2=ari(m-d,m-((m-1)/d)*d,(m-1)/d);
    		res+=(t1*t2%mod)*ph[d]%mod;
    	}
    	return (res<<1)%mod;
    }
    int main(){
    	n=read(),m=read(),init(),ans=calc();
    	ans+=mod-(2*((n-1)*n/2)*((m-1)*m/2))%mod;
    	if(n>=3) ans+=m*((n*(n-1)*(n-2)/6)%mod)%mod;
    	if(m>=3) ans+=n*((m*(m-1)*(m-2)/6)%mod)%mod;
    	printf("%lld
    ",ans%mod);
    	return 0;
    }

    欧拉函数的另一个主流的应用就是在取模方面,即欧拉定理

    $$x^{varphi(P)}equiv 1(modspace P),(gcd(x,P)=1)$$

    最经典的题目大概就是[BZOJ3884]上帝与集合的正确用法[BZOJ4869][2017六省联考]相逢是问候了。

    主要思路就是“幂次幂”,利用欧拉函数的性质,证得不断对于一个数求欧拉函数,即$varphi(varphi(...n...))$,只需$2log(n)$次直到1,随后一直不变。

    简单证明一下,对于大于$1$的整数$n$:

    若$n$为奇数,则其中必然含有一个奇质因数,因此$varphi(n)$为偶数。

    若$n$为偶数,则其中必然含有质因数$2$,因此$varphi(n)leq frac n2$。

    我也就不写具体的题解了,还是就把代码放上吧

    [BZOJ3884]上帝与集合的正确用法

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define M 500200
    #define N 10000
    using namespace std;
    LL read(){
    	LL nm=0,fh=1;char cw=getchar();
    	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
    	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
    	return nm*fh;
    }
    LL n,T,c[M],p[M],pri[M],ph[M],tmp,mod,m,cnt;
    bool vis[M];
    LL qpow(LL x,LL qs,LL md){
    	LL fin=1;
    	while(qs){
    		if(qs&1) fin=fin*x%md;
    		qs>>=1,x=x*x%md;
    	}
    	return fin;
    }
    void init(){
    	memset(vis,true,sizeof(vis)),ph[1]=1,cnt=0;
    	for(LL i=2;i<M;i++){
    		if(vis[i]) pri[++cnt]=i,ph[i]=i-1;
    		for(LL j=1;pri[j]*i<M&&j<=cnt;j++){
    			LL num=pri[j]*i; vis[num]=false;
    			if(i%pri[j]==0){ph[num]=ph[i]*pri[j];break;}
    			ph[num]=ph[i]*ph[pri[j]];
    		}
    	}
    }
    LL PHI(LL x){
    	LL fin=x,rem=x; 
    	if(x<M) return ph[x];
    	for(LL j=1;j<=cnt&&pri[j]<=rem;j++){
    		if(rem%pri[j]==0) fin/=pri[j],fin*=(pri[j]-1);
    		while(rem%pri[j]==0) rem/=pri[j];
    	}
    	if(rem>1) fin/=rem,fin*=(rem-1);
    	return fin; 
    }
    LL calc(){
    	for(p[tmp=0]=mod;p[tmp]>1;tmp++) p[tmp+1]=PHI(p[tmp]);
    	p[++tmp]=1;	LL res=2;
    	for(LL i=tmp;i;i--){
    		res%=p[i],res+=p[i];
    		res=qpow(2,res,p[i-1]);
    	}
    	return res;
    }
    int main(){
    	init(),T=read();
    	while(T--) mod=read(),printf("%lld
    ",calc());
    	return 0;
    }

    [BZOJ4869][2017六省联考]相逢是问候

    #include<algorithm>
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<cmath>
    #define LL long long
    #define M 500200
    #define N 10000
    using namespace std;
    LL read(){
    	LL nm=0,fh=1;char cw=getchar();
    	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
    	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
    	return nm*fh;
    }
    LL n,T,c[M],p[M],f[M],tpe,L,R,tg[M],pre[M];
    LL cnt,tk[M],P[1000],isp[M],pri[M],ph[M],Mi[10010][70][2],tmp,mod,m;
    bool vis[M];
    LL find(LL x){return f[x]==x?x:f[x]=find(f[x]);}
    LL qpow(LL x,LL qs,LL md){
    	LL fin=1;
    	while(qs){
    		if(qs&1) fin=fin*x%md;
    		qs>>=1,x=x*x%md;
    	}
    	return fin;
    }
    LL mul(LL kd,LL tim){return Mi[tim%N][kd][0]*Mi[tim/N][kd][1]%P[kd];}
    void add(LL pos,LL x){for(LL k=pos;k<=n;k+=(k&(-k))) (c[k]+=x)%=mod;}
    LL sum(LL pos){
    	LL tot=0ll;
    	for(LL k=pos;k>0;k-=(k&-k)) (tot+=c[k]+mod)%=mod;
    	return tot;
    }
    void init(){
    	memset(vis,true,sizeof(vis)),ph[1]=1,cnt=0;
    	for(LL i=2;i<M;i++){
    		if(vis[i]) pri[++cnt]=i,ph[i]=i-1;
    		for(LL j=1;pri[j]*i<M&&j<=cnt;j++){
    			LL num=pri[j]*i; vis[num]=false;
    			if(i%pri[j]==0){ph[num]=ph[i]*pri[j];break;}
    			ph[num]=ph[i]*ph[pri[j]];
    		}
    	}
    }
    LL PHI(LL x){
    	LL fin=x,rem=x;
    	if(x<M){return ph[x];}
    	for(LL j=1;j<=cnt&&pri[j]<=rem;j++){
    		if(rem%pri[j]==0) fin/=pri[j],fin*=(pri[j]-1);
    		while(rem%pri[j]==0) rem/=pri[j];
    	}
    	if(rem>1) fin/=rem,fin*=(rem-1);
    	return fin; 
    }
    LL calc(LL kd,LL now){ 
        for(LL i=kd;i;i--){
            if(now>=P[i]) now=now%P[i]+P[i]; 
    		now=mul(i-1,now);
            if(!now) now=P[i-1];
        }
        return now;
    }
    int main(){
    	n=read(),T=read(),mod=read(),m=read();
    	for(LL i=1;i<=n;i++) f[i]=i,p[i]=read(),pre[i]=p[i],add(i,p[i]);
    	P[0]=mod,init(),f[n+1]=n+1;
    	for(tmp=0;P[tmp]>1;tmp++) P[tmp+1]=PHI(P[tmp]);
    	P[++tmp]=1;
    	for(LL kd=0;kd<=tmp;kd++){
    		Mi[0][kd][0]=Mi[0][kd][1]=1%P[kd];
    		for(LL i=1;i<=N;i++) Mi[i][kd][0]=Mi[i-1][kd][0]*m%P[kd];
    		for(LL i=1;i<=N;i++) Mi[i][kd][1]=Mi[i-1][kd][1]*Mi[N][kd][0]%P[kd];
    	}
    	while(T--){
    		tpe=read(),L=read(),R=read();
    		if(tpe){printf("%lld
    ",(sum(R)-sum(L-1)+mod)%mod);continue;}
    		for(LL now=find(L);now<=R;now=find(now+1)){
    			tg[now]++;LL num=calc(tg[now],p[now]),dt;
    			dt=num+mod-pre[now],add(now,dt%mod);
    			if(tg[now]==tmp) f[now]=f[find(now+1)];
    			pre[now]=num;
    		}
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    按某列分表程序
    vba里设置读取背景和字体颜色
    今天写代码的一点心得!
    vba十进制转二进制
    我的心情
    数据按列拆分(可选择)
    HashMap源码分析(一):JDK源码分析系列
    HashMap源码分析(二):看完彻底了解HashMap
    JDK源码阅读(三):ArraryList源码解析
    SpringBoot使用Docker快速部署项目
  • 原文地址:https://www.cnblogs.com/OYJason/p/9465093.html
Copyright © 2011-2022 走看看