0
点赞
收藏
分享

微信扫一扫

杜教筛


杜教筛的核心

用途:

用于低于线性时间里,高效率求一些积性函数前缀和

算法:

说简单点就是整除分块+狄利克雷卷积+线性筛

公式:

时间复杂度会缩小到

积性函数

如果p和q互质并且则称f是积性函数

如果对任意p和q都有则成f是完全积性函数

常见的积性函数

  1. 除数函数,表示n的约数的k次幂和,注意是不同的 。
  2. 约数个数函数表示n的约数个数,一般也形成d(n)。
  3. 约数和函数,表示n的约数和。
  4. 欧拉函数表示不大于n切与n沪指的正整数个数,另外且对于正整数n>2来说是偶数。
  5. 莫比乌斯函数,在狄利克雷卷积的惩罚中与恒等函数互为逆元。对于无平方的因子数,,对于有平方因子数有.
  6. 元函数,狄利克雷卷积的乘法单位元,完全积性
  7. 恒等函数完全积性
  8. 单位函数,完全积性
  9. 幂函数完全积性

两个重要定理:

狄利克雷卷积

且f(n)和g(n)都是积性函数,那么满足,交换律结合律,加法分配率

莫比乌斯反演:

杜教筛

公式:

推一个具体的式子:

这里我们就要使用性质了:

枚举

//代码
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 5e6+7; //超过n^(2/3),够大了

int prime[maxn]; //记录质数
bool vis[maxn]; //记录是否被筛;
int mu[maxn]; //莫比乌斯函数值
ll phi[maxn]; //欧拉函数值
unordered_map<int,int> summu; //莫比乌斯函数前缀和
unordered_map<int,ll> sumphi; //欧拉函数前缀和
void init(){ //线性筛预计算一部分答案
int cnt = 0;
vis[0] = vis[1] = 1;
mu[1] = phi[1] = 1;
for(int i=2;i<maxn;i++){
if(!vis[i]){
prime[cnt++] = i;
mu[i] = -1;
phi[i] = i-1;
}
for(int j=0;j<cnt && i*prime[j]<maxn;j++){
vis[i*prime[j]] = 1;
if(i%prime[j]){
mu[i*prime[j]] = -mu[i];
phi[i*prime[j]] = phi[i]*phi[prime[j]];
}
else{
mu[i*prime[j]] = 0;
phi[i*prime[j]] = phi[i]*prime[j];
break;
}
}
}
for(int i=1;i<maxn;i++){ //最后,mu[]和phi[]改为记录1~maxn的前缀和。
mu[i] += mu[i-1];
phi[i] += phi[i-1];
}
}
int gsum(int x){ // g(i)的前缀和
return x;
}
ll getsmu(int x){
if(x < maxn) return mu[x]; //预计算
if(summu[x]) return summu[x]; //记忆化
ll ans = 1; //杜教筛公式中的 1
for(ll l=2,r;l<=x;l=r+1){ //用整除分块计算杜教筛公式
r = x/(x/l);
ans -= (gsum(r)-gsum(l-1))*getsmu(x/l);
}
return summu[x] = ans/gsum(1);
}
ll getsphi(int x){
if(x < maxn) return phi[x];
if(sumphi[x]) return sumphi[x]; //记忆化,每个sumphi[x]只用算一次
ll ans = x*((ll)x+1)/2; //杜教筛公式中的 n(n+1)/2
for(ll l=2,r;l<=x;l=r+1){ //用整除分块计算杜教筛公式,这里算 sqrt(x)次
r = x/(x/l);
ans -= (gsum(r)-gsum(l-1))*getsphi(x/l);
}
return sumphi[x] = ans/gsum(1);
}
int main(){
init(); //用线性筛预计算一部分
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
printf("%lld %lld\n",getsphi(n),getsmu(n));
}
return 0;
}


举报

相关推荐

0 条评论