基础筛法
埃筛
枚举质数去筛掉它的所有倍数。时间复杂度 \(O(n\log n)\),加入一些优化后时间复杂度可以优化到 \(O(n\log \log n)\)。
线性筛
仔细研究埃筛的过程,我们发现每个合数都被筛了多次,考虑优化这个过程使得每个合数都只被它的最小质因子筛掉。也就是说如果一个质数重复出现就跳出循环,体现在代码里就是一个取模,时间复杂度 \(O(n)\)
code:
void prime( ){
vis[0] = vis[1] = 1;
for(int i = 2; i <= n; i++){
if(!vis[i]) pr[++tot] = i;
for(int j = 1; j <= tot and pr[j] * i <= n; j++){
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) break;
}
}
return;
}
值得一提的是,我们可以采用线性筛筛出数论函数,但是不同的数论函数需要根据性质定制筛法。
\(\varphi(x)\):分情况讨论。
①如果 \(n\) 是质数的话 \(\varphi(n) = n - 1\)
②如果 \(p \mid n,\ \varphi(n) = (p - 1) * \varphi(\frac{n}{p})\)
③如果 \(p \nmid n,\ \varphi(n) = p \times \varphi(\frac{n}{p})\)
\(\mu(x)\):根据定义 \(\mu(1) = 1,\mu(p) = -1,p\in prime\)。
在线筛的时候每个数只会被它的最小质因数筛一遍,也就是说 \(\mu(p \times i)\) 被筛到的时候质因子数会 \(+1\),如果存在平方因子的话会直接 break 掉,所以只需要 \(\mu(p \times i) = -\mu(i)\)。
杜教筛
需要一点莫反基础。
杜教筛可以在亚线性时间复杂度内求出一个数论函数 \(f(x)\) 的前缀和 \(S(n)\),对 \(f(x)\) 的奇性不作要求。
首先证明一个对所有数论函数都适用的基本公式:
根据这个公式继续微扰:
有了这个东西我们就能除法分块了,根据式子,杜教筛使用的前提条件是这两个东西可以快速(\(O(1)\))计算:
- \(\sum_{i=1}^n(f*g)(i)\)
- \(g(i)\)
时间复杂度:\(O(n^{\frac{3}{4}})\)
code:杜教筛
点击查看代码
//杜教筛 mu,莫反筛 phi
#include <bits/stdc++.h>
#define LHK_AK_IOI true
#define ll long long
#define dub double
#define mar(x) for(int i = head[x]; i; i = e[i].nxt)
#define car(a) memset(a, 0, sizeof(a))
#define cap(a, b) memcpy(a, b, sizeof(b))
const int inf = 1e9 + 7;
const int MAXN = 1e7 + 1e5;
const int Up = 1e7;
using namespace std;
inline int read( ){
int x = 0 ; short w = 0 ; char ch = 0;
while( !isdigit(ch) ) { w|=ch=='-';ch=getchar();}
while( isdigit(ch) ) {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();}
return w ? -x : x;
}
bool vis[MAXN];
int pr[MAXN], mu[MAXN];
int smu[MAXN];
map<ll, int> _smu;
void euler(int Up){
vis[1] = vis[0] = 1; mu[1] = 1;
for(int i = 1; i <= Up; i++){
if(!vis[i]) pr[++pr[0]] = i, mu[i] = -1;
for(int j = 1; i * pr[j] <= Up and j <= pr[0]; j++){
vis[i * pr[j]] = 1;
if(i % pr[j] == 0) break;
mu[i * pr[j]] = -mu[i];
}
}
for(int i = 1; i <= Up; i++)
smu[i] = smu[i-1] + mu[i];
return;
}
ll get_smu(ll n){
if(n < Up) return smu[n];
if(_smu.find(n) != _smu.end( )) return _smu[n];
ll res = 1;
for(ll l = 2, r = 0; l <= n; l = r + 1){
r = n / (n / l);
res -= get_smu(n / l) * (r - l + 1);
}
return _smu[n] = res;
}
ll get_sphi(ll n){
ll res = 0;
for(ll l = 1, r = 0; l <= n; l = r + 1){
r = n / (n / l);
res += (n / l) * (n / l) * (get_smu(r) - get_smu(l - 1));
}
return (res + 1) >> 1;
}
signed main( ){
euler(Up);
int T = read( );
while(T--){
ll x = read( );
printf("%lld %lld\n",get_sphi(x),get_smu(x));
}
return (0x0);
}