积性函数

定义:

积性函数指对于所有互质的整数\(a\)\(b\)\(f(ab)=f(a)f(b)\)的数论函数。

容易发现:

\[f(1)=1\\ f(n)=f(\prod p_i^{a_i})=\prod f(p_i^{a_i}) \]

由这些性质我们可以发现:积性函数可以用线性筛\(O(n)\)(每次筛最小的质数)求解

性质(自己总结的):

  1. 积性函数乘上积性函数还是积性函数(显然)\(\mu(\frac{n}{d})\times d\)就是一个典型的积性函数(只要函数是积性即可,和里面的内容没关系)

  2. 积性函数的卷积形式\(\sum_{d|n}f[d]\)还是积性函数

    证明:设\((i,j)=1\)\(F[n]=\sum_{d|n}f[d]\),则:

    \[\begin{aligned} F[i\times j]&=\sum_{d|i\times j}f[d]\\&=\sum_{d1|i}\sum_{d2|j}f[d1\times d2]\\&=(\sum_{d1|i}f[d1])\times(\sum_{d2|i}f[d2])\\&=F[i]\times F[j] \end{aligned} \]

常见的积性函数

$\mu $ 莫比乌斯函数

定义:

  1. \(d\)=1,则\(\mu(d)\)=1

  2. \(d=p_1p_2\cdots p_k\),则\(\mu(d)\)=\((-1)^k\)

  3. 其他情况下,\(\mu(d)\)=0

性质:

\[F(n)=\sum_{d|n}f(d)<=>f(n)=\sum_{d|n}\mu(d)F(\frac{n}{d}) \]

可以发现:\(\mu\)的本质是容斥

所以有些题目中会给定一个非常难求的\(f(n)\)函数,但是其倍速和非常好求,就可以通过反演求出

\[\sum_{d|n}\mu[d]=[n==1] \]

这个式子非常好用,在推的时候往往只需要这个

求答案是通常用整除分块+前缀和

套路

推式子时常常有一些套路(推起来很爽的)

  1. 看见GCD,LCM,无脑想到枚举GCD,然后写成\(gcd(i,j)=\sum_{k|i,k|j}[gcd(i,j)==k]\)

  2. 想不出方法了:就把\(\sum\)往外移动

  3. 时间复杂度过高,可以把一些相乘的变量用别的变量代替,或者将一些看着的拗口的变量定义改一改

  4. 积累一些恒等式:

    \[\sum_{d|n}\mu(d)\frac{n}{d}=\phi(d) \]

例题1

B. [例题2]周期字符串 - 「数论」第7章 莫比乌斯反演 - 金牌导航 - 课程 - YbtOJ

\(f[n]\)表示答案,显然很难求,所以考虑反演

\(g[n]\)表示所有的字符串,\(g[n]=26^n\),则

\[g[n]=\sum_{d|n}f[d] \]

反演一下即可

\(\mu\)需要用map存,每次筛掉一个最小的质数,则剩下的\(\mu\)值之前一定算过

#include<bits/stdc++.h>
#define ll long long
const int p=1e9+7;
using namespace std;

const int N=1e5+5;
int n,cnt,pri[N];
map<int,int>mu;

inline int ksm(ll a,int b) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}

int main() {
	scanf("%d",&n);
	mu[1]=1; int ans=ksm(26,n);
	for(int i=2;i<=sqrt(n);i++) {
		if(n%i==0) {
			mu[i]=-1; pri[++cnt]=i;
			for(int j=1;j<=cnt&&pri[j]<i;j++) {
				if(i%pri[j]==0) {
					if((i/pri[j])%pri[j]==0) mu[i]=0;
						else mu[i]=-mu[i/pri[j]];
					cnt--;
					break;
				}
			}
			ans=((ll)mu[i]*ksm(26,n/i)+ans)%p;
	//		printf("%d %d\n",i,mu[i]);
		}
	}
	if((int)sqrt(n)*(int)sqrt(n)!=n&&n%(int)sqrt(n)==0) {
		int t=n/(int)sqrt(n);
		mu[t]=-1; pri[++cnt]=t;
		for(int j=1;j<=cnt&&pri[j]<t;j++) {
			if(t%pri[j]==0) {
				if((t/pri[j])%pri[j]==0) mu[t]=0;
					else mu[t]=-mu[t/pri[j]];
				cnt--;
				break;
			}
		}
		ans=((ll)mu[t]*ksm(26,n/t)+ans)%p;
	//	printf("%d %d\n",t,mu[t]);
	}
	for(int i=sqrt(n)-1;i;i--) {
		if(n%i==0) {
			int t=n/i;
			mu[t]=-1; pri[++cnt]=t;
			for(int j=1;j<=cnt&&pri[j]<t;j++) {
				if(t%pri[j]==0) {
					if((t/pri[j])%pri[j]==0) mu[t]=0;
						else mu[t]=-mu[t/pri[j]];
					cnt--;
					break;
				}
			}
			ans=((ll)mu[t]*ksm(26,n/t)+ans)%p;
		//	printf("%d %d\n",t,mu[t]);
		}
	}
	printf("%d\n",ans<0?ans+p:ans);
	return 0;
}

例题2

D. LCM求和 - 「数论」第7章 莫比乌斯反演 - 金牌导航 - 课程 - YbtOJ

\[\begin{aligned} \sum_{i=1}^{n}lcm(i,n)&=\sum_{i=1}^{n}\frac{in}{gcd(i,n)} \\&=n\sum_{k|n}\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}i\sum_{d|\lfloor\frac{n}{k}\rfloor,d|i}\mu(d)\\&=n\sum_{k|n}\sum_{d|\lfloor\frac{n}{k}\rfloor}\mu(d)\sum_{i=1}^{\frac{\lfloor\frac{n}{k}\rfloor}{d}}id\\&=\frac{n}{2}\sum_{k|n}\sum_{d|\lfloor\frac{n}{k}\rfloor}\mu(d)d(\frac{\lfloor\frac{n}{k}\rfloor}{d})(\frac{\lfloor\frac{n}{k}\rfloor}{d}+1)\\&=\frac{n}{2}\sum_{k|n}\sum_{d|k}\mu(d)k(\frac{k}{d}+1)\\&=\frac{n}{2}\sum_{k|n}k\sum_{d|k}\mu(d)\frac{k}{d}+\frac{n}{2}\sum_{k|n}k[k==1] \\&=\frac{n}{2}\sum_{k|n}k(\phi(k))+\frac{n}{2} \end{aligned} \]

其中\(k(\phi(k))\)是积性函数,\(\sum_{k|n}k(\phi(k))\)也是积性函数,可以通过O(n)预处理解决

但代码复杂度比较大,所以我采用了O(nlgn)的预处理方式(枚举倍数,时间复杂度是调和级数)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e6+5;
int n,cnt,pri[N];
ll f[N],g[N];
bool fl[N];

int main() {
	int T; scanf("%d",&T);
	f[1]=1;
	for(int i=2;i<=1e6;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			f[i]=(ll)i*(i-1);
		}
		for(int j=1;j<=cnt&&pri[j]*i<=1e6;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				f[i*pri[j]]=f[i]*pri[j]*pri[j];
				break;
			} else f[i*pri[j]]=f[i]*f[pri[j]];
		}
	}
	for(int i=1;i<=1e6;i++) {
		for(int j=1;j*i<=1e6;j++) {
			g[i*j]+=f[i];
		}
	}
	while(T--) {
		scanf("%d",&n);
		printf("%lld\n",(g[n]+1)*n/2); 
	}
	return 0;
}

例题3

P2568 GCD - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

直接看题解吧,懒得写了,大致就是用T代替kd

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,m,cnt,pri[M],mu[N];
ll f[N];
bool fl[N];

int main() {
	int T; scanf("%d",&T);
	mu[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i,mu[i]=-1;
		}
		for(int j=1;j<=cnt&&(ll)pri[j]*i<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				mu[i*pri[j]]=0;
				break;
			} else mu[i*pri[j]]=-mu[i];
		}
	}
	for(int i=1;i<=1e7;i++) {
		for(int j=1;j<=cnt&&(ll)pri[j]*i<=1e7;j++) {
			f[i*pri[j]]+=mu[i];
		}
	}
	for(int i=1;i<=1e7;i++) {
		f[i]+=f[i-1];
	}
	while(T--) {
		scanf("%d%d",&n,&m);
		if(n>m)swap(n,m); ll ans=0;
		for(int l=1,r;l<=n;l=r+1) {
			r=min(n/(n/l),m/(m/l));
			ans+=(f[r]-f[l-1])*(n/l)*(m/l);
		}
		printf("%lld\n",ans);
	}
	return 0;
}

例题4

G. LCM求和 - 「数论」第7章 莫比乌斯反演 - 金牌导航 - 课程 - YbtOJ

自己尝试一下或者百度

最后化成

\[=\frac{1}{4}\sum_{T=1}^{n}(\lfloor\frac{n}{T}\rfloor)(\lfloor\frac{n}{T}\rfloor+1)(\lfloor\frac{m}{T}\rfloor)(\lfloor\frac{m}{T}\rfloor+1)T\sum_{d|T}\mu(d)d \]

后面一大块是奇函数,\(O(n)\)预处理,只需要思考\(f(p^k)\)的递推关系即可

#include<bits/stdc++.h>
#define ll long long
const int p=1e8+9;
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,m,cnt,pri[M],f[N];
bool fl[N];

inline int mo(int x) {
	return x>=p?x-p:x;
}
int main() {
	f[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i,f[i]=mo((ll)i*(1-i)%p+p);
		}
		for(int j=1;j<=cnt&&(ll)pri[j]*i<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				f[i*pri[j]]=(ll)f[i]*pri[j]%p;
				break;
			} else f[i*pri[j]]=(ll)f[i]*f[pri[j]]%p;
		}
	}
	for(int i=2;i<=1e7;i++) f[i]=mo(f[i]+f[i-1]);
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%d%d",&n,&m);
		if(n>m)swap(n,m); ll ans=0;
		for(int l=1,r;l<=n;l=r+1) {
			int t1=n/l,t2=m/l;
			r=min(n/t1,m/t2);
			ans=((ll)(f[r]-f[l-1])*((ll)t1*(t1+1)/2%p)%p*((ll)t2*(t2+1)/2%p)+ans)%p;
		}
		printf("%lld\n",mo(ans+p));
	}
	return 0;
}

例题5

H. LCM计数 - 「数论」第7章 莫比乌斯反演 - 金牌导航 - 课程 - YbtOJ

上面的一题加了一个限制,可以发现相当于\(\mu\)不为0

\(\mu\)很骚,要么0,要么1,要么-1,发现\(\mu^2\)要么是0,要么是1

所以只需要乘以\(\mu^2\)即可

\[=\frac{1}{4}\sum_{T=1}^{n}(\lfloor\frac{n}{T}\rfloor)(\lfloor\frac{n}{T}\rfloor+1)(\lfloor\frac{m}{T}\rfloor)(\lfloor\frac{m}{T}\rfloor+1)T\sum_{d|T}\mu(d)\mu^2(\frac{T}{d})d \]

还是积性函数,思考\(f(p^k)\)的关系,发现\(k\geq 3\)时恒为0,所以暴力搞一搞即可

#include<bits/stdc++.h>
#define ll long long
const int p=1<<30;
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,m,cnt,pri[M],f[N];
bool fl[N];

int main() {
	f[1]=1;
	for(int i=2;i<=4e6;i++) {
		if(!fl[i]) {
			pri[++cnt]=i,f[i]=((ll)i*(1-i)%p+p)%p;
		}
		for(int j=1;j<=cnt&&(ll)pri[j]*i<=4e6;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				if((i/pri[j])%pri[j]) {
					f[i*pri[j]]=((ll)f[i/pri[j]]*-pri[j]%p*pri[j]%p*pri[j]%p+p)%p;
				} else f[i*pri[j]]=0;
				break;
			} else f[i*pri[j]]=(ll)f[i]*f[pri[j]]%p;
		}
	}
	for(int i=2;i<=4e6;i++) f[i]=(f[i]+f[i-1])%p;
	int T; scanf("%d",&T);
	while(T--) {
		scanf("%d%d",&n,&m);
		if(n>m)swap(n,m); ll ans=0;
		for(int l=1,r;l<=n;l=r+1) {
			int t1=n/l,t2=m/l;
			r=min(n/t1,m/t2);
			ans=((ll)(f[r]-f[l-1])*((ll)t1*(t1+1)/2%p)%p*((ll)t2*(t2+1)/2%p)+ans)%p;
		}
		printf("%lld\n",(ans+p)%p);
	}
	return 0;
}

欧拉函数 \(\phi\)

性质

\[\phi(n)=n\prod_{i=1}^{k}(1-\frac{1}{p_i}) \]

\[\sum_{d|n}\phi(d)=n \]

  1. 欧拉定理:对于\((a,m)=1\),(保证存在逆元)存在

\[a^{\phi(m)}=1(\% m) \]

  1. 扩展欧拉定理:在任意情况下,当\(m>\phi(p)\),有

\[a^m=a^{m\%\phi(p)+\phi(p)} \]

例题1

ybtoj D. 互质个数

观察发现:

\[(m!,k)=(m!,m!+k) \]

所以:

\[S=\frac{n!}{m!}\phi(m!) \]

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,m,p,phi[N],jc[N],inv[N];
bool fl[N];
int cnt,pri[M];
int main(){
	int T; scanf("%d%d",&T,&p);
	phi[1]=1; jc[0]=jc[1]=1,inv[0]=inv[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&(ll)pri[j]*i<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				phi[i*pri[j]]=phi[i]*pri[j]; 
				break;
			} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
		if(!fl[i]) phi[i]=(ll)phi[i-1]*(i-1)%p;
			else phi[i]=(ll)phi[i-1]*i%p; 
		jc[i]=(ll)jc[i-1]*i%p;
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
	}
	for(int i=2;i<=1e7;i++) {
		inv[i]=(ll)inv[i-1]*inv[i]%p;
	}
	while(T--) {
		scanf("%d%d",&n,&m); 
		printf("%lld\n",(ll)inv[m]*jc[n]%p*phi[m]%p);
	}
	return 0;
}

例题2

Luogu P4140 奇数国

\[S=\phi(\prod_{i=a}^{b}x_i) \]

由于没法除,所以考虑加起来,考虑线段树,显然\(\phi\)值和质数的个数有关,而质数只有60个,所以开60棵线段树即可,但发现只有单点修改,区间查询,所以改成树状数组

#include<bits/stdc++.h>
#define ll long long
const int p=19961993;
using namespace std;

const int N=1e5+5;
int n,cnt,pri[100],a[N],ans;
struct A{
	int c[N];
	inline void add(int x,int k) {
		for(;x<=n;x+=x&-x) c[x]+=k;
	}
	inline int ask(int x) {
		int ret=0;
		for(;x;x-=x&-x) ret+=c[x];
		return ret;
	}
}tr[61];
inline int ksm(ll a,int b) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1;
	}
	return ret;
}
int main() {
	int T; scanf("%d",&T); n=1e5;
	for(int i=2;i<=281;i++) {
		bool fl=0;
		for(int j=2;j<i;j++) {
			if(i%j==0) {
				fl=1; break;
			}
		}
		if(!fl) pri[++cnt]=i;
	}
	for(int i=1;i<=n;i++) {
		a[i]=3,tr[2].add(i,1);
	}
	while(T--) {
		int op,x,y; scanf("%d%d%d",&op,&x,&y);
		if(!op) {
			ans=1;
			for(int i=1;i<=60;i++) {
				int t=tr[i].ask(y)-tr[i].ask(x-1);
				if(t) {
					ans=(ll)ans*(pri[i]-1)%p*ksm(pri[i],t-1)%p;
				}
			}
			printf("%d\n",ans);
		} else {
			for(int i=1;i<=60;i++) {
				int num=0;
				for(;a[x]%pri[i]==0;a[x]/=pri[i]) num++;
				tr[i].add(x,-num);	
			}
			a[x]=y;
			for(int i=1;i<=60;i++) {
				int num=0;
				for(;y%pri[i]==0;y/=pri[i]) num++;
				tr[i].add(x,num);
			}
		}
	}
	return 0;
}

例题3

ybtoj F. 函数求和

提示:枚举质数,见小本本

TLE了一次:暴力枚举质数是\(O(n\sqrt n)\) 会TLE

所以先\(O(n)\)的线性筛预处理出所有数的最小质因子,然后暴力除即可,时间复杂度大致为\(O(nlgn)\)

#include<bits/stdc++.h>
#define ll long long
const int p=1e9+7;
using namespace std;

const int N=1e7+5,M=1e6+5;
int n,inv[N],c[N],cnt,pri[M],lst[N];
bool fl[N];

int main() {
	inv[0]=inv[1]=1;
	for(int i=2,j;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i,lst[i]=i;
		} 
		for(j=1;j<=cnt&&(ll)i*pri[j]<=1e7;j++) {
			fl[i*pri[j]]=1;
			lst[i*pri[j]]=pri[j];
			if(i%pri[j]==0) break;
		}
		inv[i]=(ll)(p-p/i)*inv[p%i]%p;
		c[i]=1;
	}
	scanf("%d",&n);
	for(int i=1,j;i<=n;i++) {
		int t,tt; scanf("%d",&t);
		for(;lst[t]>1;) {
			j=lst[t];
			tt=1;
			for(;t%j==0;t/=j) tt=(ll)tt*j%p;
			tt=(ll)tt*j%p;
			c[j]=(ll)c[j]*(tt-1)%p*inv[j-1]%p;
		}
	}
	ll ans=1;
	for(int i=1;i<=1e7;i++) {
		if(c[i]>1) {
			ans=ans*((ll)(i-1)%p*inv[i]%p*(c[i]-1)%p+1)%p;
		}
	}
	printf("%lld\n",(ans+p)%p);
	return 0;
}

例题4

ybtoj 集合统计

很妙的混泥土数学的下取整章节的应用(不应该忽视的)

WA了一次:n,m是\(10^{15}\),不能直接乘,都要先取模

#include<bits/stdc++.h>
#define ll long long
const int p=998244353;
using namespace std;

ll n,m;
int main() {
	scanf("%lld%lld",&n,&m);
	ll ans=(n%p)*(m%p)%p,t=n;
	for(int i=2;i<=sqrt(t);i++) {
		if(t%i==0) {
			n=n/i*(i-1);
			for(;t%i==0;t/=i);		
		}
	}
	if(t>1) n=n/t*(t-1);
	ans=ans*(n%p)%p;
	t=m;
	for(int i=2;i<=sqrt(t);i++) {
		if(t%i==0) {
			m=m/i*(i-1);
			for(;t%i==0;t/=i);		
		}
	}
	if(t>1) m=m/t*(t-1);
	ans=ans*(m%p)%p;
	printf("%lld\n",ans);
	return 0;
}

例题5

ybtoj H. 最小数

很经典的操作,但后来想到BSGS去了,完全没想到欧拉定理

\[10^{m}=1(\% p) \]

若(10,p)=1,则\(m|\phi(p)\),反证法

见小本本

WA了一次: 需要快速加,因为模数是long long

#include<bits/stdc++.h>
#define ll long long
using namespace std;

ll n,p;
inline ll mo(ll x) {
	return x>=p?x-p:x;
}
inline ll ksj(ll a,ll b) {
	ll ret=0;
	while(b) {
		if(b&1) ret=mo(ret+a);
		a=mo(a+a),b>>=1;
	}
	return ret;
}
inline ll ksm(ll a,ll b) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ksj(ret,a);
		a=ksj(a,a),b>>=1; 
	}
	return ret;
}
int main() {
	int id=0;
	while(scanf("%lld",&n),n!=0) {
		id++; printf("Case %d: ",id);
		if(n%8==0) n/=8;
			else if(n%4==0) n/=4;
				else if(n%2==0) n/=2;
		n=n*9;
		if(n%2==0||n%5==0) {
			puts("0"); continue;
		}
		p=n;
		ll t=n;
		for(int i=2;i<=sqrt(t);i++) {
			if(t%i==0) {
				n=n/i*(i-1);
				for(;t%i==0;t/=i);		
			}
		}
		if(t>1) n=n/t*(t-1);
		bool fl=0;
		for(int i=1;i<=sqrt(n);i++) {
			if(n%i==0) {
				if(ksm(10,i)==1) {
					printf("%d\n",i);
					fl=1; break;
				}
			}
		}
		if(!fl) {
			for(int i=sqrt(n);i>=1;i--) {
				if(n%i==0) {
					if(ksm(10,n/i)==1) {
						printf("%lld\n",n/i);
						break;
					}
				}
			}
		}
	}
	return 0;
}

例题6

ybtoj I. 幂的取模

这是无限,本来以为需要解\(2^x=x\)这种情况,但好像这是错的

想到用扩展欧拉定理,直接套用即可

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=1e7+5,M=1e6+5;
int cnt,pri[M],phi[N];
bool fl[N];

inline int ksm(ll a,int b,int p) {
	ll ret=1;
	while(b) {
		if(b&1) ret=ret*a%p;
		a=a*a%p,b>>=1; 
	}
	return ret;
}
int ask(int x) {
	if(x==1) return 0;
	return ksm(2,ask(phi[x])+phi[x],x);
}
int main() {
	phi[1]=1;
	for(int i=2;i<=1e7;i++) {
		if(!fl[i]) {
			pri[++cnt]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=cnt&&(ll)i*pri[j]<=1e7;j++) {
			fl[i*pri[j]]=1;
			if(i%pri[j]==0) {
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			} else phi[i*pri[j]]=phi[i]*(pri[j]-1);
		}
	}
	int T; scanf("%d",&T);
	while(T--) {
		int p; scanf("%d",&p);
		printf("%d\n",ask(p));
	}
	return 0;
}
posted @ 2021-03-26 15:37  wwwsfff  阅读(319)  评论(1编辑  收藏  举报