积性函数相关
mod998244353 · · 个人记录
以下内容中,
前置知识:特别基础的数论
可能更好的阅读体验
积性函数
这部分设
一些数论函数:
- 莫比乌斯函数:
\mu(n)=\begin{cases}1\qquad n=1\\(-1)^k\qquad c_1=c_2=\cdots=c_k=1\\0\qquad\operatorname{otherwise}\end{cases} - 欧拉函数(表示小于等于
n 且与n 互质的数的个数)\varphi(n)=\begin{cases}1\qquad\qquad\qquad\qquad n=1\\n\prod\limits_{i=1}^k(1-\dfrac{1}{p_i})\qquad\operatorname{otherwise}\end{cases} - 常函数:
I(n)=1 - 单位函数:
\epsilon(n)=[n=1] (若p 成立则[p] 是1,否则[p] 为0) - 恒等函数:
id(n)=n - 因数个数函数:
d(n)=\sum\limits_{k|n}1 - 因数和函数:
\sigma(n)=\sum\limits_{k|n}k - 素因子个数函数:
\Omega(n)=\sum\limits_{i=1}^kc_i - 奇怪的函数
\lambda(n)=(-1)^{\Omega(n)} - 不同素因子个数函数:
\omega(n)=k
积性函数:对于任意互质的两个数
如
完全积性函数:对于任意的两个数
如
求欧拉函数和莫比乌斯函数
分解质因数,按照其定义和计算公式计算即可。
#include<bits/stdc++.h>
using namespace std;
int getphi(int n) {//phi
int tmp=n,ans=n;
for(int i=2; i*i<=tmp; ++i)
if(!(tmp%i)) {//满足这个的每一个i都是质数,因为之前所有质因子都被除掉了
ans-=ans/i;//即ans=ans*(i-1)/i;
while(!(tmp%i))tmp/=i;
}
return tmp>1?ans-ans/tmp:ans;//最后一个质因数要判断
}
int getmu(int n) {//mu
int tmp=n,ans=1;
for(int i=2; i*i<=tmp; ++i)
if(!(tmp%i)) {
ans=-ans;//乘上-1
tmp/=i;
if(!(tmp%i))//指数大于1
return 0;
}
return tmp>1?-ans:ans;//最后一个质因数的指数一定为1
}
int n;
int main() {
scanf("%d",&n);
printf("%d %d\n",getphi(n),getmu(n));
return 0;
}
欧拉函数和莫比乌斯函数的线性筛
bool isp[MAXN];
int prime[MAXN],pcnt;
void init(int n) {
for(int i=2; i<=n; ++i) {
if(!isp[i])prime[++pcnt]=i;//0
for(int j=1,x; j<=pcnt&&(x=prime[j]*i)<=n; ++j) {
isp[prime[j]*i]=1;
if(!(i%prime[j]))break;//1
}
}
}
在上面的欧拉筛的代码中,
根据两个函数的定义,当
首先它们都是积性函数,所以没有break时,直接相乘即可。
根据莫比乌斯函数的定义,当
当
可以发现
那就可以线性筛了
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAXN=6000500;
bool isp[MAXN+1];
int phi[MAXN+1],mu[MAXN+1],pcnt,n,prime[MAXN+1];
int main() {
mu[1]=phi[1]=1;
for(int i=2; i<=MAXN; ++i) {
if(!isp[i]) {
prime[++pcnt]=i;
mu[i]=-1;
phi[i]=i-1;
}//素数时的值
for(int j=1,x; j<=pcnt&&(x=i*prime[j])<=MAXN; ++j) {
isp[x]=true;
if(i%prime[j]) {
mu[x]=-mu[i];
phi[x]=phi[i]*(prime[j]-1);
} else {
mu[x]=0;
phi[x]=phi[i]*prime[j];
break;
}//合数时的值
}
}
return 0;
}
来几道题:
P2303 Longge 的问题
先用一个
提出
后面的部分其实就是
就可以计算了,时间复杂度
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,ans;
ll getphi(ll n) {
ll tmp=n,ans=n;
for(ll i=2; i*i<=tmp; ++i)
if(!(tmp%i)) {
ans-=ans/i;
while(!(tmp%i))tmp/=i;
}
return tmp>1?ans-ans/tmp:ans;
}
int main() {
scanf("%lld",&n);
for(ll i=1,k; i*i<=n; ++i)
if(!(n%i)) {
k=n/i;
ans+=getphi(k)*i;
if(i^k)ans+=getphi(i)*k;
}
printf("%llu\n",ans);
return 0;
}
P2158 仪仗队
我们可以假设C君的位置为
那么队伍可以看做是一个
我们可以不考虑
我们可以发现整个图形是对称的,所以只用算一半,乘上
我们可以发现,对于一个点
所以能看到的
设
则这一半的个数为
由
注意要特判
#include <bits/stdc++.h>
using namespace std;
const int MAXN=40005;
bool isp[MAXN];
int prime[MAXN],pcnt,phi[MAXN],ans;
void init(int n) {
ans=phi[1]=1;
for(int i=2; i<=n; ++i) {//线性筛phi
if(!isp[i]) {
prime[++pcnt]=i;
phi[i]=i-1;
}
for(int j=1,x; j<=pcnt&&(x=prime[j]*i)<=n; ++j) {
isp[prime[j]*i]=1;
if(i%prime[j]) {
phi[x]=phi[i]*phi[prime[j]];
} else {
phi[x]=phi[i]*prime[j];
break;
}
}
ans+=phi[i];//顺便求和
}
}
int main() {
int n,q;
scanf("%d",&n);
init(--n);
printf("%d\n",n==0?0:ans*2+1);//特判0
return 0;
}
P1390 公约数的和
设
可以得到答案为
接下来考虑求
首先
对于
把
把
后面的
所以
所以原式
括号内线性筛+前缀和预处理,直接
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e7+100;
int phi[MAXN],cnt,n,prime[MAXN];
ll sum[MAXN],ans;
int main() {
scanf("%d",&n);
sum[1]=phi[1]=1;
for(int i=2,p; i<=n; ++i) {//线性筛欧拉函数
if(!phi[i])prime[++cnt]=i,phi[i]=i-1;
for(int j=1; j<=cnt; ++j) {
p=prime[j]*i;
if(p>n)break;
if(!(i%prime[j])) {
phi[p]=phi[i]*prime[j];
break;
}
phi[p]=phi[i]*(prime[j]-1);
}
sum[i]=sum[i-1]+phi[i];//欧拉函数的前缀和
}
for(int i=1; i<=n; ++i)
ans+=sum[n/i]*i-i;
printf("%lld\n",ans);
return 0;
}
P2398 GCD SUM
和上题差不多,可以算是双倍经验。
原式
可以发现前面两个都是上题要求的,后者就是
把上题的式子代进来:
这个是经典结论,可以记一下。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=1e7+100;
int phi[MAXN],cnt,n,prime[MAXN];
ll sum[MAXN],ans;
int main() {
scanf("%d",&n);
sum[1]=phi[1]=1;
for(int i=2,p; i<=n; ++i) {
if(!phi[i])prime[++cnt]=i,phi[i]=i-1;
for(int j=1; j<=cnt; ++j) {
p=prime[j]*i;
if(p>n)break;
if(!(i%prime[j])) {
phi[p]=phi[i]*prime[j];
break;
}
phi[p]=phi[i]*(prime[j]-1);
}
sum[i]=sum[i-1]+phi[i];
}
for(int i=1; i<=n; ++i)
ans+=((sum[n/i]<<1)-1)*i;
printf("%lld\n",ans);
return 0;
}