数论其二

1.数论函数的定义:

指定义域是正整数的函数。

2.积性函数与完全积性函数:

积性函数:若a,b互质,且f(ab)=f(a)f(b),那么函数f(x)是积性函数

完全积性函数:a,b,有f(ab)=f(a)f(b),那么函数f(x)是完全积性函数

3.常用的数论函数

idk(n)=nk,ϵ(n)=[n==1],σk(n)=d|ndk

4.欧拉函数:

定义:

1n中与 n 互质的数的个数是欧拉函数,记作ϕ(n)

计算式:

n=p1c1×p2c2×...×pmcm (p1,p2...pm 为质数),则:

ϕ(n)=n×p11p1×p21p2×...×pm1pm=n×Πpprime,p|n(11p)

性质:

①:欧拉函数是积性函数,即若a,b互质,则ϕ(ab)=ϕ(a)ϕ(b)

②:1n中与 n 互质的数的和是  nϕ(n)2

③:如果 p 是质数,p|np2|n,则ϕ(n)=ϕ(n/p)×p

④:如果 p 是质数,p|np2|n,则ϕ(n)=ϕ(n/p)×(p1)

⑤:d|nϕ(d)=n,用卷积表示是ϕI=id,即欧拉反演

5.欧拉定理和扩展欧拉定理:

欧拉定理:若正整数 a,n 互质,则aϕ(n)1(mod n)

扩展欧拉定理:

若正整数 a,n 互质,则对于任意正整数 b,有

abab mod ϕ(n) (mod n)

对于不互质的情况,有

{abab mod ϕ(n),b<ϕ(n)abab mod ϕ(n)+ϕ(n),bϕ(n)(mod n)

P5091 【模板】扩展欧拉定理

while(b=getchar())
    if(!(b<'0'||b>'9'))
        break;
while(b>='0'&&b<='9'){//处理非常大的数的技巧
    num=num*10+b-'0';
    if(num>=phi)
        num=num%phi,mod=1;//phi表示phi(m)
    b=getchar();
}
if(mod) num+=phi;
printf("%lld\n",fpow(a,num));//fpow为快速幂

P4139 上帝与集合的正确用法

直接应用扩展欧拉定理,然后递归暴算,直到模数为 1 停止。

long long work(long long mod){
	if(mod==1)
		return 0;
	return f(2,work(phi[mod])+phi[mod],mod);//f为快速幂
}
//主函数中:
while(t--){
	scanf("%d",&n);
	printf("%lld\n",work(n));
}

P2480 [SDOI2010] 古代猪文

一句活题意:求

gd|nCndmod 999911659

如果g=999911659,那么答案是 0

否则,因为 9999111659 是质数,所以 g999911659 互质。根据欧拉定理,有:

ans=gd|nCnd(mod 999911658)(mod 999911659)

关键就是求d|nCnd(mod 999911658)。直接求比较困难,因为模数太大,无法应用卢卡斯定理降低复杂度。

考虑将 999911658 质因数分解,得到 2×3×4679×35617

可以暴力枚举 n 的因数,然后用卢卡斯定理分别求出 d|nCnd2,3,4679,35617 的结果,记为 a1,a2,a3,a4 ,然后求同余方程组:

{xa1(mod 2)xa2(mod 3)xa3(mod 4679)xa4(mod 35617)

就能得到d|nCnd(mod 999911658),然后快速幂算出ans即可。

code:

signed main(){
    scanf("%lld%lld",&n,&g);
    if(g==999911659){//别忘了特判
        printf("0\n");
        return 0;
    }
    for(int i=1;i<=4;++i)
        pre(p[i],i);//预处理阶乘和阶乘逆元
    for(int i=1;i*i<=n;++i){
        if(n%i==0){
            for(int j=1;j<=4;++j)
                a[j]=(a[j]+dfs(n,i,p[j],j))%p[j];//卢卡斯定理
            if(i*i!=n){
                for(int j=1;j<=4;++j)
                    a[j]=(a[j]+dfs(n,n/i,p[j],j))%p[j];
            }
        }
    }
    for(int i=1;i<=4;++i){
        int tmp;
        exgcd(sum/p[i],p[i],tmp,y);
        tmp=tmp*a[i]%p[i];
        tmp=tmp*(sum/p[i]);
        x+=tmp;
    }
    x=(x+sum)%sum;
    printf("%lld\n",fpow(g,x,mod));
    return 0;
}

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

回忆一下《上帝与集合的正确用法》,可知:对于一个数,使用题目中的操作,操作一定次数后就会变为一个固定的数。可以证明,操作次数是对数级别的。

所以,类似于区间开方,修改时先暴力修改,直到变成固定的数后,就打上标记,以后不再修改它。

首先设 a[i][j] 表示第 i 个数进行 j 次操作后的值,然后预处理出所有的 a[i][j]

接下来考虑线段树。线段树除了维护区间和以外,还要维护一个 idid 表示该节点代表的区间所有的数都已经操作了 j 次。特别地, id=1 表示该节点代表的区间所有的数的 id 值不完全相同。

然而,如果直接套用《上帝与集合的正确用法》中的方法处理 a[i][j] ,复杂度是O(nlog3n),会超时。可以对快速幂进行优化,将复杂度优化到O(nlog2n)。具体而言,先预处理出 fpow1[i](i[1,10000]) 表示 cifpow2[i]表示 c10000i 。假设我们要计算 ck ,那么我们只需要算出 x=k/10000,y=k%10000 ,就有ck=fpow1[y]×fpow2[x]

code:

void pre_work(int now){
    ++cnt;
    phi[cnt]=now;int tmp=now;
    for(int i=2;i*i<=now;++i)
        if(tmp%i==0){
            while(tmp!=1&&tmp%i==0)
                tmp/=i;
            phi[cnt]=phi[cnt]/i*(i-1);
        }
    if(tmp!=1)
        phi[cnt]=phi[cnt]/tmp*(tmp-1);
    if(phi[cnt]==1)
        return ;
    pre_work(phi[cnt]);
}
int fpow(int x,int y,int mod){
    int s=x;x=1;bool ok=0;
    while(y){
        if(y&1) x=x*s;
        y>>=1;s=s*s;
        if(s>=mod&&y) ok=1,s%=mod;
        if(x>=mod) ok=1,x%=mod;
    }
    if(x>=mod) ok=1;
    return ok?x%mod+mod:x%mod;
}
void pre_work2(){
    for(int j=1;j<=cnt;++j){
        fpow1[0][j]=fpow2[0][j]=1;
        int tmp=fpow(c,10000,phi[j]);
        bool ok1=0,ok2=0;
        if(c>=2) ok2=1;
        for(int i=1;i<=10000;++i){
            fpow1[i][j]=fpow1[i-1][j]*c;
            if(fpow1[i][j]>=phi[j]||ok1)
                fpow1[i][j]=fpow1[i][j]%phi[j]+phi[j],ok1=1;
            fpow2[i][j]=fpow2[i-1][j]*tmp;
            if(fpow2[i][j]>=phi[j]||ok2)
                fpow2[i][j]=fpow2[i][j]%phi[j]+phi[j],ok2=1;
        }
    }
}
int work(int w,int now,int cnt){
    if(now==cnt+1){
        if(w>=phi[now])
            return w%phi[now]+phi[now];
        return w%phi[now];
    }
    int tmp=work(w,now+1,cnt),x,y;
    x=tmp/10000;y=tmp%10000;
    x=fpow2[x][now]*fpow1[y][now];
    if(x>=phi[now])
        x=x%phi[now]+phi[now];
    return x;
}
void push_up(int u){
    p[u].w=(p[u<<1].w+p[u<<1|1].w)%mod;
    if(p[u<<1].id!=-1&&p[u<<1].id==p[u<<1|1].id)
        p[u].id=p[u<<1].id;
    else p[u].id=-1;
}
void build(int u,int l,int r){
    p[u].l=l;p[u].r=r;
    if(l==r){
        p[u].w=a[l][0];
        return ;
    }
    int mid=(l+r)>>1;
    build(u<<1,l,mid);build(u<<1|1,mid+1,r);
    push_up(u);
}
void add(int u,int l,int r,int w){
    if(p[u].id==cnt-1)
        return ;
    if(p[u].l==p[u].r){
        p[u].id+=w;
        p[u].w=a[p[u].l][p[u].id];
        return ;
    }
    int mid=(p[u].l+p[u].r)>>1;
    if(mid>=l)
        add(u<<1,l,r,w);
    if(mid<r)
        add(u<<1|1,l,r,w);
    push_up(u);
}
int ask(int u,int l,int r){
    if(p[u].l>=l&&p[u].r<=r)
        return p[u].w;
    int mid=(p[u].l+p[u].r)>>1,re=0;
    if(mid>=l)
        re=(re+ask(u<<1,l,r))%mod;
    if(mid<r)
        re=(re+ask(u<<1|1,l,r))%mod;
    return re;
}
signed main(){
    scanf("%lld%lld%lld%lld",&n,&m,&mod,&c);
    for(int i=1;i<=n;++i)
        scanf("%lld",&a[i][0]);
    phi[cnt=1]=mod;
    pre_work(mod);
    phi[++cnt]=1;
    pre_work2();
    for(int i=1;i<=n;++i)
        for(int j=1;j<cnt;++j)
            a[i][j]=work(a[i][0],1,j)%mod;
    build(1,1,n);
    for(int i=1;i<=m;++i){
        scanf("%lld%lld%lld",&opt,&l,&r);
        if(opt==0)
            add(1,l,r,1);
        else
            printf("%lld\n",ask(1,l,r));
    }
    return 0;
}

5.莫比乌斯函数:

定义:

μ(n)={1,n=1(1)k,n=p1p2...pk0,other

莫比乌斯反演:

形式0(因数形式):设 F(n)=d|nf(d),即 F=If ,则 f=μF

形式1(倍数形式):设 F(n)=n|df(d) ,则 f(n)=n|dμ(dn)f(d)

线性求欧拉函数和莫比乌斯函数:

for(int i=2;i<=1e7;++i){
	if(!a[i])
		a[i]=i,p[++tot]=i;
	for(int j=1;j<=tot;++j){
		if(p[j]*i>1e7||p[j]>a[i])
			break;
		a[p[j]*i]=p[j];
	}
}
phi[1]=miu[1]=1;
for(int i=2;i<=1e7;++i){
	int tmp=i/a[i];
	if(tmp%a[i]==0){
		phi[i]=phi[tmp]*a[i];
		miu[i]=0;
	}
	else{
		phi[i]=phi[tmp]*(a[i]-1);
		miu[i]=miu[tmp]*-1;
	}
}

P1390公约数的和

简单莫反题。要求

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

可以先考虑问题的简化版:

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

=d=1ndi=1nj=1n[gcd(i,j)==d]

=d=1ndi=1ndj=1nd[gcd(i,j)==1]

=d=1ndi=1ndj=1ndk|gcd(i,j)gcd(i,j)μ(k)

=d=1ndi=1ndμ(i)i|jndi|knd1

=d=1ndi=1ndμ(i)i|jndi|knd1

=d=1ndi=1ndμ(i)ndi2

然后用一个数论分块,就可以求出该柿子。

再考虑题目中要求我们算的柿子,设它为ans

不难发现i=1nj=1ngcd(i,j)=ans×2+i=1ngcd(i,i)=ans×2+n×(n+1)2

然后就能愉快地求出来了。

P1447能量采集

不难发现,题目让我们求的是i=1nj=1m2×gcd(i,j)n×m

于是就转化成为了上一题。ans=2×d=1ndi=1ndμ(i)ndimdin×m(这里假定n>=m

YY的GCD

莫反+一个小优化。先假定n>=m。仍是按照第一题的方式推柿子,ans=d=1ni=1ndμ(i)ndimdi(dprime)

然而这样复杂度是O(n logn)的,仍会超时,肿么办?

注意到,数对 (i,d) 相当于枚举了所有乘积小于等于 n 的数(且后一个数是质数)。令 T=i×d,考虑枚举每一个 T 对答案有多少贡献(这是一个套路,后面会经常用):

ans=T=1nnTmTk|T,kprimeμ(Tk)

然后发现, k|T,kprimeμ(Tk) 是可以预处理的。枚举每一个质数 p ,然后令它的倍数 k 加上 μ(kp)

code:

void work(){
	ll r=0,a=n,b=m;
	if(a<b) swap(a,b);
	for(int i=1;i<=b;i=r+1){
		r=min(a/(a/i),b/(b/i));
		if(r>b) r=b;
		ans+=(sum[r]-sum[i-1])*(a/i)*(b/i);//注意是a/i下取整,要加括号 
	}
}
int main(){
	for(int i=1;i<=maxn;++i)
		miu[i]=1;
	for(int i=2;i<=maxn;++i){
		if(!vis[i]){
			p[++tot]=i,miu[i]=-1;
			for(int j=i*2;j<=maxn;j+=i){
				vis[j]=1;
				if(j/i%i==0) miu[j]=0;
				else miu[j]*=-1;
			}
		}
	}
	for(int i=1;i<=maxn;++i)
		for(int j=1;p[j]*i<=maxn&&j<=tot;++j)
			s[i*p[j]]+=miu[i];
	for(int i=1;i<=maxn;++i)
		sum[i]=sum[i-1]+s[i];
	scanf("%lld",&t);
	while(t--){
		scanf("%lld%lld",&n,&m);
		ans=0;
		work();
		printf("%lld\n",ans);
	}
	return 0;
}

P3327约数个数和

前言:一些常用的二级结论:

μ(ij)=μ(i)μ(j)[gcd(i,j)==1]

d(ij)=x|iy|j[gcd(x,y)==1]

ϕ(ij)=ϕ(i)ϕ(j)gcd(i,j)ϕ(gcd(i,j))

推柿子参看题解区

然后令F(n)=i=1nni,则

ans=g=1min(n,m)μ(g)F(ng)F(mg)

然后用数论分块即可。时间复杂度 O(n n+T n)

code;

void work(){
    scanf("%lld%lld",&n,&m);
    if(n<m)
        swap(n,m);
    int ans=0;
    for(int i=1,r=0,rr=0;i<=m;i=r+1){
        r=n/(n/i);rr=m/(m/i);//cout<<r<<" "<<rr<<endl;
        r=min(r,min(rr,m));//cout<<m/i<<" "<<m/r<<" "<<n/i<<" "<<n/r<<endl;
        ans+=(miu[r]-miu[i-1])*s[m/i]*s[n/i];
    }
    printf("%lld\n",ans);
}
signed main(){
    scanf("%lld",&t);
    for(int i=2;i<=100000;++i){
        if(a[i]==0)
            p[++tot]=i,a[i]=i;
        for(int j=1;j<=tot;++j){
            if(i*p[j]>100000||a[i]<p[j])
                break;
            a[i*p[j]]=p[j];
        }
    }
    for(int i=1;i<=100000;++i)
        miu[i]=1;
    for(int i=2;i<=100000;++i){
        int tmp=i/a[i];
        if(tmp%a[i]==0)
            miu[i]=0;
        else
            miu[i]=-miu[tmp];
    }
    for(int i=1;i<=100000;++i)
        miu[i]+=miu[i-1];
    for(int i=1;i<=100000;++i){
        for(int j=1,r=0;j<=i;j=r+1){
            r=i/(i/j);
            s[i]+=(r-j+1)*(i/j);
        }
    }
    while(t--)
        work();
    //for(int i=1;i<=100;++i)cout<<miu[i]<<" ";cout<<endl;
    return 0;
}

P3768简单的数学题

利用 ϕI=id 可以得到:

d=1nF2(nd)d2ϕ(d)

其中 F(n)=n×(n+1)/2

其中 d=1nF2(nd) 可以用数论分块处理,剩下的部分考虑杜教筛。

剩下的部分是 d2ϕ(d) ,设 d=1nd2ϕ(d)s(n) ,考虑构造函数 g(n)=n2 ,则有:

s(n)=i=1n((id2ϕ)g)(i)i=2nid2(i)s(ni)

化简得到:

s(n)=(n×(n+1)2)2i=2ni2s(ni)

P3312数表

先不考虑 a 的限制,有:

ans=i=1nj=1mk|gcd(i,j)k

=i=1nj=1mf(gcd(i,j))

=d=1ni=1ndj=1mdf(d)[gcd(i,j)==d]

=d=1ni=1ndj=1mdf(d)k|gcd(i,j)μ(k)

=d=1nf(d)k=1mdμ(k)ndkmdk

=t=1mntmtkd=tμ(k)f(d)

(其中 f(x) 表示 x 的因数和,可以预处理得到)

接下来考虑 a 。首先将询问按照 a 从小到大排序,然后开一个树状数组(其中单点i表示 (fμ)(i) ),每次遇到可以加入树状数组的 f(i) ,就枚举 i 的倍数,令 ki 的单点数值增加 f(i)×μ(k) 。然后在数论分块的时候,如果当前区间为 [l,r] ,就让 ans 乘以 [l,r] 的区间和。

code:

bool cmp(node a,node b){
    return a.a<b.a;
}
bool cmp2(node a,node b){
    return a.w<b.w;
}
void add(long long x,long long w){
    while(x<=L)
        c[x]=(c[x]+w)%mod,x+=x&(-x);
}
long long ask(long long x){
    long long re=0;
    while(x)
        re=(re+c[x])%mod,x-=x&(-x);
    return re;
}
long long work(long long n,long long m){
    int ans=0;
    if(n<m)
        swap(n,m);
    for(int i=1,r=0;i<=m;i=r+1){
        r=min(n/(n/i),min(m/(m/i),m));
        ans=(ans+((n/i)%mod*(m/i)%mod*((ask(r)-ask(i-1)+mod)%mod)%mod))%mod;
    }
    return ans;
}
void pre_work(){
	for(int i=2;i<=L;++i){
		if(a[i]==0)
			a[i]=i,prime[++tot]=i;
		for(int j=1;j<=tot;++j){
			if(prime[j]*i>L||prime[j]>a[i])
				break;
			a[i*prime[j]]=prime[j];
		}
	}
	miu[1]=1;
	for(int i=2;i<=L;++i){
		int j=i/a[i];
		if(j%a[i]==0)
			miu[i]=0;
		else
			miu[i]=miu[j]*(-1);
	}
    for(int i=1;i<=tot;++i){
        long long now=prime[i];
        p[now][0]=sum[now][0]=1;
        long long tmp=0;
        while(sum[now][tmp]<=L){
            ++tmp;
            p[now][tmp]=p[now][tmp-1]*prime[i];
            sum[now][tmp]=(sum[now][tmp-1]+p[now][tmp]);
        }
    }
    for(int i=1;i<=L;++i){
        long long tmp=i,cnt=0;
        f[i].w=1;f[i].id=i;
        for(int j=1;j<=tot&&prime[j]*prime[j]<=i;++j){
            cnt=0;
            while(tmp%prime[j]==0)
                tmp/=prime[j],++cnt;
            f[i].w=f[i].w*sum[prime[j]][cnt];
        }
        if(tmp!=1)
            f[i].w=f[i].w*sum[tmp][1];
    }
    sort(f+1,f+L+1,cmp2);
}
signed main(){
    pre_work();
	scanf("%lld",&t);
    for(int i=1;i<=t;++i)
        scanf("%lld%lld%lld",&v[i].n,&v[i].m,&v[i].a),v[i].id=i;
    sort(v+1,v+t+1,cmp);
    for(int i=1,j=1;i<=t;++i){
        while(j<=L&&f[j].w<=v[i].a){
            for(int k=f[j].id;k<=L;k+=f[j].id)
                add(k,f[j].w*miu[k/f[j].id]%mod);
            ++j;
        }
        ans[v[i].id]=work(v[i].n,v[i].m);
    }
    for(int i=1;i<=t;++i)
        printf("%lld\n",ans[i]);
	return 0;
}

P1891疯狂LCM

lcm(i,n)n×i/gcd(n,i) 表示,于是原式化为:

nd|ni=1di[gcd(i,d)==1]

引理:i=1ni[gcd(i,n)==1]=ϕ(n)×n2

证明:左边的柿子表示[1,n]中与n互质的数的和,而[1,n]中与n互质的数是成对出现的,每一对的和为 n ,所以与 n 互质的数的平均值为 n2 ,和为 ϕ(n)×n2

因此,上述柿子就化为:

nd|nϕ(d)×d2

f(n)=d|nϕ(d)×d2 ,则 f(n) 可以预处理得到。外层的 i 循环到 n ,内层的 j 循环到 ni ,每次令 f(i×j)+=ϕ(i)×i2 即可。

P4449 于神之怒加强版

i=1nj=1mgcd(i,j)k

=d=1ndki=1ndi=1md[gcd(i,j)==1]

=d=1ndki=1ndi=1mdt|gcd(i,j)μ(t)

=d=1ndkt=1ndμ(t)ndtmdt

=T=1nnTmTd|Tdkμ(Td)

其中,d|Tdkμ(Td) 预处理即可。

P3704 [SDOI2017] 数字表格

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

=d=1nfdi=1ndj=1md[gcd(i,j)==1]

=d=1nfdi=1ndj=1mdt|gcd(i,j)μ(t)

=d=1nfdt=1ndμ(t)ndtmdt

=T=1n(d|Tfdμ(Td))nTmT

其中,d|Tfdμ(Td) 预处理即可。

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