亚线性筛

所谓的亚线性筛,是指一类用低于线性复杂度求出积性函数前缀和的方法。

杜教筛

杜教筛可以在\(O(n^{\frac{2}{3}})\)的时间复杂度求出\(\sqrt{n}\) 个点值,原理和实现都比较简单。

原理与实现

对于数论函数\(f\),要求计算\(S(n)=\sum\limits_{i=1}^{n}{f(i)}\)。对于任意的数论函数\(g\),有

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

如果可以快速求出\(\sum\limits_{i=1}^n{(g*f)(i)}\)以及\(g\)\(\sqrt{n}\)个前缀和,再用数论分块递归求解即可得到\(S(n)\)。因此关键是找到合适的\(g\),使得\(g*f\)可以快速求和。

假设求和计算复杂度为\(O(1)\),直接计算时间复杂度为\(O(\int_0^{n^{\frac{1}{2}}}{\sqrt{\frac{n}{x}}=O(n^{\frac{3}{4}})})\),如果使用线性筛预处理前\(n^{\frac{2}{3}}\)项的结果,时间复杂度就变为\(O(\int_0^{n^{\frac{1}{3}}}{\sqrt{\frac{n}{x}}=O(n^{\frac{2}{3}})})\),可以处理\(10^{11}\)级别的数据。

常用的乘性函数的求和:

  • 莫比乌斯函数:\(\mu * 1=\epsilon\)\(\epsilon(n) = [n=1]\)

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

  • 欧拉函数:\(\varphi * 1 = \operatorname{id}\)\(\operatorname{id}(n)=n\)

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

  • 约数个数:\(\operatorname{d} * \mu=1\)(需要\(\mu\)前缀和)

\[S(n)=n-\sum_{i=2}^{n}{\mu(i)S(\lfloor\frac{n}{i}\rfloor)} \]

Powerful Number筛

Powerful Number(下面简称PN)筛可以说是杜教筛的扩展,可以结合杜教筛求一些积性函数的前缀和。

要求:

假设要求\(F(n)=\sum\limits_{i=1}^{n}{f(i)}\),如果存在函数\(g\)满足:

  • \(g\)是积性函数
  • \(g\)前缀和\(G\)容易求得
  • 对于质数\(p\)\(g(p)=f(p)\)

那么就可以较快的求出\(F(n)\)

原理与实现

\(f\)可以写成\(f=h*g\),那么有(\(h\)\(g\)都是积性函数)

\[\begin{align} F(n) &=\sum\limits_{i=1}^{n}{(g*h)(i)} \\ &=\sum\limits_{i=1}^{n}{\sum\limits_{d|n}{h(d)g(\frac{n}{d})}} \\ &= \sum\limits_{d=1}^{n}{h(d)\sum\limits_{i=1}^{\lfloor\frac{n}{d}\rfloor}{g(i)}} \\ &= \sum\limits_{d=1}^{n}{h(d)G(\lfloor\frac{n}{d}\rfloor)} \end{align} \]

如果可以使得\(p\)为质数时有\(h(p)=0\),即\(f(p)=h(p)+g(p)=g(p)\)时,就有当\(d\)中含有次数为1的质因子时上面式子中\(h(d)=0\),相当于把含有次数为1质因子的\(d\)全部都“筛掉”了。将所有不含1次质因子的数称为PN,就有

\[F(n)= \sum\limits_{d=1, d \ {\rm is \ PN}}^{n}{h(d)G(\lfloor\frac{n}{d}\rfloor)} \]

这是很强力的筛选,因为\(n\)以内的PN只有\(O(\sqrt{n})\)个。

PN筛分为以下几个步骤:

  1. 构造\(g\)

注意构造的\(g\)要是积性函数,且满足\(f(p)=g(p)\),比如

  • \(f(p^c)=p\),那么构造\(g(x)=x\)

  • \(f(p^c)=p^c(p^c-1)\),那么构造\(g(x)=id(x)\varphi(x)\)

\(g\)的前缀和\(G\)要好求,注意到需要的是\(\lfloor\frac{n}{i}\rfloor\)处的\(G\)值,所以常用杜教筛。

  1. 计算\(h(p^c)\)

知道了\(g\)\(f\)就可以求出\(h\),由

\[f(p^c)=\sum_{i=0}^{c}{h(p^i)g(p^{c-i})} \]

可得

\[h(p^c)=h(p^c)g(1)=f(p^c)-\sum_{i=0}^{c-1}{h(p^i)g(p^{c-i})} \]

可以枚举\(p\)\(c\)递推预处理出来。

  1. 搜索PN,过程中结果累和起来

直接枚举质因子以及次数搜索,由于PN只有\(O(\sqrt{n})\)个,只用搜索\(O(\sqrt{n})\)次。全部结果加起来就是答案。

如果处理\(G\)的时间复杂度为\(O(n^{\alpha})\),那么总时间复杂度为\(O(\max(\sqrt{n},n^{\alpha}))\)

例题

题目:P5325 【模板】Min_25筛

\(f(p^c)=p^c(p^c-1)\),那么构造\(g(x)=id(x)\varphi(x)\)

可以发现\((id\cdot\varphi)*id=id^2\),所以可以使用杜教筛,即

\[G(n)=\sum\limits_{i=1}^{n}{i^2}-\sum_{i=2}^{n}{i \cdot G(\lfloor\frac{n}{i}\rfloor)} \]

然后各种预处理\(h(p^c)\),直接搜索PN累加答案即可。

#include <bits/stdc++.h>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N) 
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f

const int N = 6e6 + 10;
const ll MX = 1e10 + 10;
const int M = 1e9 + 7;
const double eps = 1e-5;


ll pri[N];
bool isnp[N];
int cnt;

inline ll qpow(ll a, ll b, ll m) {
	ll res = 1;
	while(b) {
		if(b & 1) res = (res * a) % m;
		a = (a * a) % m;
		b = b >> 1;
	}
	return res;
}

namespace PNS {
	map<ll, ll> mpG;
	ll g[N], G[N];
	ll h[N][35];
	ll global_n, ans;
	ll r6, r2;
	void init() { // 预处理素数和h[p^c]
		r2 = qpow(2, M - 2, M);
		r6 = qpow(6, M - 2, M);
		isnp[1] = 1;
		g[1] = 1;
		for(int i = 2; i < N; i++) {
			if(!isnp[i]) {
				pri[cnt++] = i;
				g[i] = 1ll * i * (i - 1) % M;
			}
			for(int j = 0; j < cnt && i * pri[j] < N; j++) {
				isnp[i * pri[j]] = 1;
				if(i % pri[j] == 0) {
					g[i * pri[j]] = g[i] * pri[j] % M * pri[j] % M;
					break;
				}
				g[i * pri[j]] = g[i] * g[pri[j]] % M;
			}
		}
		for(int i = 1; i < N; i++) G[i] = (g[i] + G[i - 1]) % M;
		for(int i = 0; i < cnt; i++) {
			h[i][0] = 1;
			h[i][1] = 0;
		}
		for(int i = 0; i < cnt; i++) {
			ll pp = pri[i] * pri[i], rp = qpow(pri[i], M - 2, M);
			for(int c = 2; c < 35; c++) {
				ll pw = pp % M;
				h[i][c] = pw * (pw - 1) % M;
				for(int k = 0; k < c; k++) {
					h[i][c] = (h[i][c] - h[i][k] * pw % M * pw % M * (pri[i] - 1) % M * rp % M + M) % M;
					pw = pw * rp % M;
				}
				if(pp > MX / pri[i]) break;
				pp = pp * pri[i];
			}
		}
	}

	ll sum(ll n) { // 杜教筛预处理
		if(n < N) return G[n];
		if(mpG.count(n)) return mpG[n];
		ll res = n % M * (n % M + 1) % M * (2 * n % M + 1) % M * r6 % M;
		ll i = 2;
		while(i <= n) {
			ll j = n / (n / i);
			res = (res - (j - i + 1) % M * ((i + j) % M) % M * r2 % M * sum(n / i) % M + M) % M;
			i = j + 1;
		}
		mpG[n] = res;
		return res;
	}

	void dfs(int p, ll hv, ll v) { // 搜索PN, hv为h函数值,v为当前找到的PN
		ans = (ans + hv * sum(global_n / v) % M) % M;
		for(int i = p; i < cnt && v <= global_n / pri[i] / pri[i]; i++) {
			ll tv = v * pri[i] * pri[i];
			for(int c = 2; ; c++) {
				dfs(i + 1, hv * h[i][c] % M, tv);
				if(tv > global_n / pri[i]) break;
				tv = tv * pri[i];
			}
		}
	}
	
	ll solve(ll n) {
		global_n = n;
		sum(n); 
		ans = 0;
		dfs(0, 1, 1);
		return ans;
	}
}

int main() {
	IOS;
	PNS::init();
	ll n;
	cin >> n;
	cout << PNS::solve(n) << endl;
}

min25筛

min25筛 学习笔记

posted @ 2021-09-04 20:02  limil  阅读(412)  评论(0编辑  收藏  举报