积性函数
定义:
积性函数指对于所有互质的整数\(a\)和\(b\)有\(f(ab)=f(a)f(b)\)的数论函数。
容易发现:
由这些性质我们可以发现:积性函数可以用线性筛\(O(n)\)(每次筛最小的质数)求解
性质(自己总结的):
-
积性函数乘上积性函数还是积性函数(显然)\(\mu(\frac{n}{d})\times d\)就是一个典型的积性函数(只要函数是积性即可,和里面的内容没关系)
-
积性函数的卷积形式\(\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 $ 莫比乌斯函数
定义:
-
若\(d\)=1,则\(\mu(d)\)=1
-
若\(d=p_1p_2\cdots p_k\),则\(\mu(d)\)=\((-1)^k\)
-
其他情况下,\(\mu(d)\)=0
性质:
可以发现:\(\mu\)的本质是容斥
所以有些题目中会给定一个非常难求的\(f(n)\)函数,但是其倍速和非常好求,就可以通过反演求出
这个式子非常好用,在推的时候往往只需要这个
求答案是通常用整除分块+前缀和
套路
推式子时常常有一些套路(推起来很爽的)
-
看见GCD,LCM,
无脑想到枚举GCD,然后写成\(gcd(i,j)=\sum_{k|i,k|j}[gcd(i,j)==k]\) -
想不出方法了:就把\(\sum\)往外移动
-
时间复杂度过高,可以把一些相乘的变量用别的变量代替,或者将一些看着的拗口的变量定义改一改
-
积累一些恒等式:
\[\sum_{d|n}\mu(d)\frac{n}{d}=\phi(d) \]
例题1
B. [例题2]周期字符串 - 「数论」第7章 莫比乌斯反演 - 金牌导航 - 课程 - YbtOJ
设\(f[n]\)表示答案,显然很难求,所以考虑反演
设\(g[n]\)表示所有的字符串,\(g[n]=26^n\),则
反演一下即可
\(\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
其中\(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
自己尝试一下或者百度
最后化成
后面一大块是奇函数,\(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\)即可
还是积性函数,思考\(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\)
性质
- 欧拉定理:对于\((a,m)=1\),(保证存在逆元)存在
- 扩展欧拉定理:在任意情况下,当\(m>\phi(p)\),有
例题1
观察发现:
所以:
#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
由于没法除,所以考虑加起来,考虑线段树,显然\(\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
提示:枚举质数,见小本本
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
很妙的混泥土数学的下取整章节的应用(不应该忽视的)
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
很经典的操作,但后来想到BSGS去了,完全没想到欧拉定理
若(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
这是无限,本来以为需要解\(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;
}