基础筛法

埃筛

枚举质数去筛掉它的所有倍数。时间复杂度 \(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)\) 的奇性不作要求。
首先证明一个对所有数论函数都适用的基本公式:

\[\sum_{i=1}^n(f*g)(i) = \sum_{i=1}^n\sum_{d \mid i}g(d)f(\lfloor \frac{i}{d}\rfloor)=\sum_{d = 1}^ng(d)S(\lfloor \frac{n}{d}\rfloor) \]

根据这个公式继续微扰:

\[g(1)S(n) = \sum_{i=1}^n(f*g)(i)-\sum_{i=2}g(i)S(\lfloor \frac{n}{i}\rfloor) \]

有了这个东西我们就能除法分块了,根据式子,杜教筛使用的前提条件是这两个东西可以快速(\(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);
}
posted @ 2023-07-08 21:26  Kun_9  阅读(21)  评论(0编辑  收藏  举报