欧拉函数的一些题

欧拉函数及相关定理


1.P2158 [SDOI2008] 仪仗队

原题链接


题目大意

在一个从(0,0)(n1,n1)的方阵中,有多少点能从原点“看到”


分析

一个点D能从原点“看到”,说明对于任何一个D前的点T,都有直线OD不经过T,即kODkOT

那么什么时候kODkOT呢?

发现kOD=YDXD

kODkOT,当且仅当gcd(XD,YD)=1
(若gcd(XD,YD)=d,则必存在点T(XDd,YDd),使得OT经过D

那么就有

s=i=0n1j=0n1[gcd(i,j)=1]

这个式子其实可以莫反,不过没必要

把能看到的点画出来,可以发现它们关于l:y=x对称,可以只计算l:y=x以下的部分

l:y=x及以下能看到的点有

i=0n1j=0i[gcd(i,j)=1]

j=0i[gcd(i,j)=1]

表示表示小于等于i的与i互质的数的个数
恰好是φ(i)

s=2i=0n1φ(i)1

(点(1,1)被多算了一次)

code
#include<bits/stdc++.h>
#define il inline
#define cs cosnt
#define ri register
using namespace std;
const int N=40001;
int fi[N],b[N],cnt,ss[664580],mx;
inline int done(const int &n){
    if(!n) return 0;//
    register int ans=3;
    for(register int i=2;i<=n;++i){
        if(!b[i]) ss[++cnt]=i,fi[i]=i-1;
        for(register int j=1;j<=cnt&&ss[j]*i<=n;++j){
            b[i*ss[j]]=1;
            if(i%ss[j]) fi[i*ss[j]]=fi[i]*fi[ss[j]];
            else{fi[i*ss[j]]=fi[i]*ss[j];break;} 
        }
        ans+=2*fi[i];
    }
    return ans;
}
int main(){
    int n;
    cin>>n;
    cout<<done(n-1);
    return 0;
} 

2.P5091 【模板】扩展欧拉定理

原题链接


题目大意

abmodm


分析

扩展欧拉定理的模板题,不过要注意ababmodφ(p)+φ(p)modpgcd(a,p)1,b>φ(p)时才适用

code
#include<bits/stdc++.h>
#define il inline
#define ri register
#define cs const
using namespace std;

namespace Q{
    il int rd(){
        ri int x=0,f=1;ri char c=getchar();
        while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
        return x*f;
    }
    il void wt(int x){
        if(x<0) x=-x,putchar('-');
        if(x>=10) wt(x/10);
        return putchar(x%10+48),void();
    }
    il int qpow(int a,int b,int p){
        ri int as=1;
        while(b>0){
            if(b&1) as=1ll*as*a%p;
            a=1ll*a*a%p,b>>=1;
        }
        return as;
    }
    il int varphi(int p){
        ri int res=p;
        for(ri int i=2;i*i<=p;++i){
            if(p%i==0){
                res=res/i*(i-1);
                while(p%i==0){
                    p/=i;
                }
            }
        }
        if(p>1) res=res/p*(p-1);
        return res;
    }
    il int mo(int mod){//读入的时候顺便取模
        ri int x=0,f=0;ri char c=getchar();
        while(c<'0'||c>'9') c=getchar();
        while(c>='0'&&c<='9'){
            x=x*10+(c^48);
            if(x>mod) x%=mod,f=1;//判断一下b是不是大于\varphi(m)
            c=getchar();
        }
        return x+f*mod;//如果大于就加\varphi(m)
    }
} using namespace Q;

int a,m,b;
signed main(){
    a=rd(),m=rd(),b=mo(varphi(m));
    wt(qpow(a,b,m));
    return 0;
}

3.P4139 上帝与集合的正确用法

原题链接


题目大意

定义a0=1,an=2an1,可以证明anmodpn足够大时为常数,求该常数


分析

不难发现,就是要求s=222

s为什么是常数?
考虑扩展欧拉定理

ab{abmodφ(p)gcd(a,p)=1abgcd(a,p)1,bφ(p)abmodφ(p)+φ(p)gcd(a,p)1,b>φ(p)(modp)

这里指数222必然大于φ(p),可以用ababmodφ(p)+φ(p)modp这个式子

那么,对于每一层,都有

222=222modφ(p)+φ(p)modp

可以证明在logp以内,φ(φ(φ(p)))会等于1
所以虽然222有无限层,但是φ(p)=1之后的层对答案不会产生影响,
可以递归求解,边界φ(p)=1,此时指数为1

code
#include<bits/stdc++.h>
using namespace std;
inline void rd(int &x){
    register bool f=x=0;register char c=getchar();
    while(c<'0'||c>'9') f|= (c=='-'), c=getchar();
    while(c>='0'&c<='9')x=x*10+(c^48),c=getchar();
}
inline void wt(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>=10) wt(x/10);
    putchar(x%10+48);
}
const int T=1e3+1,N=1e7+1;
int t,p[T],fi[N],b[N],cnt,ss[664580],mx;
inline void init(const int &n){
    for(register int i=2;i<=n;++i){
        if(!b[i]) ss[++cnt]=i,fi[i]=i-1;
        for(register int j=1;j<=cnt&&ss[j]*i<=n;++j){
            b[i*ss[j]]=1;
            if(i%ss[j]) fi[i*ss[j]]=fi[i]*fi[ss[j]];
            else{fi[i*ss[j]]=fi[i]*ss[j];break;} 
        }
    }
}
inline int qpow(register int a,register int b,const int &m){
    register int ans=1;
    while(b){
        if(b&1) ans=(1ll*ans*a)%m;
        a=(1ll*a*a)%m,b>>=1;
    }
    return ans;
}
inline int done(const int &m){
    if(m==1) return 0;
    return qpow(2,done(fi[m])+fi[m],m);
}
int main(){
    rd(t);
    for(register int i=1;i<=t;++i){
        rd(p[i]),mx=max(mx,p[i]);
    }
    init(mx);
    for(register int i=1;i<=t;++i){
        wt(done(p[i])),putchar('\n');
    }
    return 0;
} 

4.Power Tower

原题链接


题目大意

给定一个数列w1,w2,...,wn和模数p,每次询问一个区间[l,r],求wlwl+1wl+2...wrmodp的值


分析

还是用扩展欧拉定理递归计算,注意判断指数是否大于φ(p)(我直接在快速幂里处理的),递归边界φ(p)=1或递归到rφ(p)可以线性筛预处理,也可以O(p)计算

code
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
int n,m,q,w[N];
map<int,int> fi;
inline int rd(){
    register int x=0;register char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x;
}
inline void wt(int x){
    if(x>=10) wt(x/10);
    putchar(x%10+48);
}
inline void getfi(int n){
    register int ans=n,nn=n;
    for(int i=2;i*i<=n;i++){
        if(nn%i==0){
            ans/=i;ans*=(i-1);
            while(nn%i==0) nn/=i;
        }
    }
    if(nn>1) ans/=nn,ans*=(nn-1);
    fi[n]=ans;
}
inline int qpow(int a,int b,int p){
    register int ans=1;
    while(b>0){
        if(b&1){
            ans=ans*a;
            if(ans>=p) ans=ans%p+p;
        }
        a=a*a;
        if(a>=p) a=a%p+p;
        b>>=1;
    }
    return ans;
}
inline int done(int i,int p,int r){
    if(i>r||p==1) return 1;
    return qpow(w[i],done(i+1,fi[p],r),p);
}
signed main(){
    n=rd(),m=rd();
    for(register int i=1;i<=n;i++) w[i]=rd();
    register int ff=m;
    while(ff!=1) getfi(ff),ff=fi[ff];fi[ff]=1;
    q=rd();
    register int l,r;
    while(q--){
        l=rd(),r=rd();
        wt(done(l,m,r)%m),putchar('\n');
    }
}

5.P2568 GCD

原题链接


题目大意

1x,yngcd(x,y)为素数的数对(x,y)有多少对。


分析

做法比较玄学
感谢理解一下,
gcd(x,y)=p可以写成gcd(xp,yp)=1
a=xpb=ypab),
gcd(a,b)=1的数对有φ(a)
考虑枚举a,则p要满足apn,即pna
可以在欧拉筛时对每一个i统计i以内的质数个数,记为b(i)
于是

s=2i=1nφ(i)b(ni)φ(1)b(n)=2i=2nφ(i)b(ni)+b(n)

注意a=b=1时只算一对(不用乘2)
复杂度O(n)

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
cs int N=1e7+9;
int b[N],ss[N],tot,fi[N];
il void init(int n){
    fi[1]=1;
    for(ri int i=2;i<=n;++i){
        if(!b[i]) ss[++tot]=i,fi[i]=i-1;
        b[i]=tot;
        for(ri int j=1;j<=tot&&ss[j]*i<=n;++j){
            b[i*ss[j]]=1;
            if(i%ss[j]) fi[i*ss[j]]=fi[i]*fi[ss[j]];
            else{fi[i*ss[j]]=fi[i]*ss[j];break;}
        }
    }
}
int main(){
    int n;
    long long as=0;
    cin>>n;
    init(n);
    for(ri int i=2;i<n;++i){
        as+=fi[i]*b[n/i];
    }
    cout<<as*2+tot;
    return 0;
} 

6.P2398 GCD SUM

原题链接


题目大意

i=1nj=1ngcd(i,j)


分析

欧拉反演做法在莫比乌斯反演里提到过了,这里不再赘述

最终式子

s=d=1nφ(d)nd2

复杂度O(n+n)

code(欧拉反演)
//题解上粘的代码
#include <cstdio>
#include <cstring>

const int MAXN = 100010;

int prime[MAXN], v[MAXN], phi[MAXN], sumPhi[MAXN], cnt, n;

void eular(int n) {//线性筛筛欧拉函数
    memset(v, 0, sizeof(v));
    sumPhi[1] = phi[1] = 1;

    for (int i = 2; i <= n; i++) {
        if (!v[i]) {
            prime[++cnt] = v[i] = i;
            phi[i] = i - 1;
        }
        for (int j = 1; j <= cnt; j++) {
            if (prime[j] > v[i] || i * prime[j] > n) break;
            v[i * prime[j]] = prime[j];
            phi[i * prime[j]] = phi[i] * (i % prime[j] ? prime[j] - 1 : prime[j]);
        }
        sumPhi[i] = sumPhi[i - 1] + phi[i];//求欧拉函数的前缀和,如果整除分块的话就要用
    }
}

int main() {
    scanf("%d", &n);
    eular(n);

    long long ans = 0;
    for (int l = 1, r; l <= n; l = r + 1) {//这里是整除分块写法,如果不懂可以直接for 1 to n
        r = n / (n / l);
        ans += (long long) (sumPhi[r] - sumPhi[l - 1]) * (n / l) * (n / l);
    }
    printf("%lld\n", ans);
    return 0;
}

7.LCMSUM - LCM Sum

原题链接


题目大意

i=1nlcm(i,n) (1n106)


分析

具体做法莫比乌斯反演里写过了,推到最后都会用到φ,这里也不再赘述

最后可以推出

s=n2(1+d|nφ(d)d)

复杂度O(nlogn+Q)

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
#define int long long//记得开long long!
using namespace std;
cs int N=1e6;
int ss[N],fi[N+1],f[N+1],cnt;
il int rd(){
    ri int x=0,f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f; 
}
il void wt(int x){
    if(x<0) x=-x,putchar('-');
    if(x>=10) wt(x/10);
    putchar(x%10+48);
}
il void init(int n){
    for(ri int i=2;i<=n;++i){
        if(!fi[i]) fi[i]=i-1,ss[++cnt]=i;
        for(ri int j=1;j<=cnt&&ss[j]*i<=n;++j){
            if(i%ss[j]) fi[i*ss[j]]=fi[i]*fi[ss[j]];
            else {fi[i*ss[j]]=fi[i]*ss[j]; break;}
        }
    }
    for(ri int i=2;i<=n;++i){
        for(ri int j=i;j<=n;j+=i){
            f[j]+=fi[i]*i/2;
        }
    }
    return;
}
signed main(){
    int t=rd(),n;init(N);
    while(t--) n=rd(),wt(n*(1+f[n])),putchar('\n');
    return 0;
} 

8.拿行李(极限版) GCD - Extreme (II)

原题链接


题目大意

i=1nj=i+1ngcd(i,j)


分析

由于这题有多组数据(还不知道有多少组),所以考虑刷表
1n4×106+1的答案预处理出来,然后O(1)查询

s(n)=i=1n1gcd(i,n)

那么有as(n)=i=1ns(i)

考虑快速求s(n)

s(n)=i=1n1gcd(i,n)=d|ndi=1n1[gcd(i,n)=d]=d|ndi=1nd1[gcd(i,nd)=1]=d|nd(φ(nd)[gcd(nd,nd)=1])=d|nd(φ(nd)[nd=1])=d|n,dndφ(nd)

复杂度O(nlnn+Q)

code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
#define int long long
using namespace std;
il int rd(){
    ri int x=0,f=1;ri char c=getchar();
    while(c<'0'||c>'9') f=(c=='-')?-1:1,c=getchar();
    while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
    return x*f;
}
il void wt(int x){
    if(x<0) x=-x,putchar('-');
    ri short tl=0,a[15]={0};
    do{a[++tl]=x%10,x/=10;}while(x);
    for(;tl;--tl)putchar(a[tl]+48);
}
cs int N=4e6+1;
int ss[N],cnt,vs[N+5],fi[N+5];
int n,m,as[N+5];
il void init(int n){
    as[1]=fi[1]=0;//为方便就令varphi(1)=0了
    for(ri int i=2;i<=n;++i){
        if(!vs[i]) ss[++cnt]=i,fi[i]=i-1;
        for(ri int j=1;j<=cnt&&ss[j]*i<=n;++j){
            vs[i*ss[j]]=1;
            if(i%ss[j])fi[i*ss[j]]=fi[i]*fi[ss[j]];
            else{fi[i*ss[j]]=fi[i]*ss[j];break;}
        }
        as[i]=fi[i];
    }
    for(ri int i=2;i*i<=n;++i){
        as[i*i]+=fi[i]*i;
        for(ri int j=i+1;j*i<=n;++j){
            as[j*i]+=fi[i]*j+fi[j]*i;
        }
    }
    for(ri int i=2;i<=n;++i) as[i]+=as[i-1]; 
}
signed main(){
    init(N);
    n=rd();
    while(n){
        wt(as[n]);
        putchar('\n');		
        n=rd();
    } 
    return 0;
}

edit

posted @   雨夜风月  阅读(108)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示