min_25筛学习小记

min_25筛是洲阁筛的简化版,虽然我并不会洲阁筛。

min_25筛可以筛一些特殊积性函数的前缀和,有些不是积性函数也可以筛,比如说最大真因子。

同杜教筛一样,同时筛出了所有 ⌊ n i ⌋ \lfloor {n \over i} \rfloor in的前缀和。

至于min_25能筛的积性函数有哪些要求,在博客后面会讨论

所有时间复杂度证明见朱大佬2018国家预备队论文。

筛的本质:

1 − n 1-n 1n内的所有数的最小质因子都会不大于 n \sqrt n n ,同欧拉筛法,我们就是在枚举到一个数的最小质因子的时去统计到它。

Step 1:

洲阁筛的第一步就是求出n以内所有质数p的 ∑ f ( p ) \sum f(p) f(p)

f ( p ) f(p) f(p)一般是和p有关的多项式,如果 f ( p ) f(p) f(p)能够拆成若干个完全积性函数的式子,那么这个 f f f就是可以筛的。

这个可能有点难理解,可以看下面的式子来得到。

以筛质数个数为例,显然有 f ( p ) = 1 f(p)=1 f(p)=1

预处理 n \sqrt n n 以内的质数,第j大的质数设为 p j p_j pj

g ( n , j ) g(n,j) g(n,j)表示 2 − n 2-n 2n的数中,要么是质数,要么最小质因子大于 p j p_j pj,把合法的数当作质数算的答案。

解释一下何为当作质数:
对于一个质数 p p p f ( p ) f(p) f(p)一般是和 p p p有关的式子,对于一个 x x x,我们不考虑它是不是质数,而是直接像质数那样算出和 x x x有关的值。

g ( n , j ) = { g ( n , j − 1 ) p j 2 > n g ( n , j − 1 ) − f ( p j ) [ g ( n p j , j − 1 ) − ∑ i = 1 j − 1 f ( p i ) ] p j 2 < = n g(n,j)= \begin{cases} g(n,j-1)&p_j^2>n\\ g(n,j-1)-f(p_j)[g(\frac{n}{p_j},j-1)-\sum_{i=1}^{j-1}f(p_i)]&p_j^2<= n \end{cases} g(n,j)={g(n,j1)g(n,j1)f(pj)[g(pjn,j1)i=1j1f(pi)]pj2>npj2<=n

第二条式子中,因为 g g g的定义含有质数,而我们的目的是使 p j p_j pj成为最小质因子,所以要减掉它们。

由于用到的n一定是 ⌊ n i ⌋ \lfloor {n \over i} \rfloor in,所以只用记录 n \sqrt n n 个值。
初值:
在求质数个数中, g ( n , 0 ) = n − 1 g(n,0)=n-1 g(n,0)=n1,其它题中,可能是和n有关的奇奇怪怪的东西。

题目:LOJ#6235. 区间素数个数

for(ll i = 1, j; i <= n; i = j + 1) {
	j = n / (n / i); w[++ m] = n / i;
	if(w[m] <= sqr) id1[w[m]] = m; else id2[n / w[m]] = m;
	g[m] = w[m] - 1;
}
fo(j, 1, p[0]) for(int i = 1; i <= m && p[j] * p[j] <= w[i]; i ++) {
	int k = (w[i] / p[j] <= sqr) ? id1[w[i] / p[j]] : id2[n / (w[i] / p[j])];
	g[i] += - g[k] + j - 1;
}

显然 g g g只有 g ( n , p 0 ) g(n,p0) g(n,p0)才有用,p0为 n \sqrt n n 以内的质数个数,为了方便,后面用 g ( n ) g(n) g(n)表示 g ( n , p 0 ) g(n,p0) g(n,p0)

以上写法复杂度为 O ( n 3 4 l o g   n ) O({n^{3 \over 4} \over log~n}) O(log nn43)

Step 2:

递归版:

知道了 g g g就可以求答案了。

s ( n , j ) s(n,j) s(n,j)表示2-n中的所有数,最小质因子大于等于 p j p_j pj的所有数x的 ∑ f ( x ) \sum f(x) f(x)

s ( n , j ) = g ( n ) − ∑ i = 1 j − 1 f ( p i ) + ∑ k = j p 0 ∑ e ∣ e > = 1 和 p k e + 1 < = n   f ( p k e ) ∗ s ( n / p k e , k + 1 ) + f ( p k e + 1 ) s(n,j)=g(n)-\sum_{i=1}^{j-1}f(p_i)+\sum_{k=j}^{p0} \sum_{e|e>=1和p_k^{e+1}<=n}~f(p_k^e)*s(n/p_k^e,k+1)+f(p_k^{e+1}) s(n,j)=g(n)i=1j1f(pi)+k=jp0ee>=1pke+1<=n f(pke)s(n/pke,k+1)+f(pke+1)

不用hash,直接强行递归复杂度为 O ( n 3 4 l o g   n ) O({n^{3 \over 4} \over log~n}) O(log nn43)

题目:LOJ#6053. 简单的函数

#include
#include
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
using namespace std;

const int mo = 1e9 + 7, ni2 = 5e8 + 4;

const int N = 2e5 + 5;

ll n, w[N];
int m, sqr, i1[N], i2[N];
int bz[N], p[N], sp[N];
int h[N], g[N];

void sieve(int n) {
	fo(i, 2, n)	 {
		if(!bz[i]) p[++ p[0]] = i, sp[p[0]] = (sp[p[0] - 1] + i) % mo;
		for(int j = 1; i * p[j] <= n; j ++) {
			bz[i * p[j]] = 1;
			if(i % p[j] == 0) break;
		}
	}
}

int dg(ll x, int y) {
	if(x <= 1 || p[y] > x) return 0;
	int k = x <= sqr ? i1[x] : i2[n / x];
	int s = ((ll) g[k] - h[k] - (sp[y - 1] - (y - 1)) + mo + mo) % mo;
	if(y == 1) s = (s + 2) % mo;
	for(int i = y; i <= p[0] && (ll) p[i] * p[i] <= x; i ++) {
		ll p1 = p[i], p2 = (ll) p[i] * p[i];
		for(int e = 1; p2 <= x; e ++, p1 = p2, p2 *= p[i])
			s = (s + (ll) dg(x / p1, i + 1) * (p[i] ^ e) % mo + (p[i] ^ (e + 1))) % mo;
	}
	return s;
}

int main() {
	scanf("%lld", &n);
	sqr = sqrt(n); sieve(sqr);
	for(ll i = 1, j; i <= n; i = j + 1) {
		j = n / (n / i); w[++ m] = n / i;
		if(w[m] <= sqr) i1[w[m]] = m; else i2[n / w[m]] = m;
		h[m] = (w[m] - 1) % mo;
		g[m] = (w[m] - 1) % mo * ((w[m] + 2) % mo) % mo * ni2 % mo;
	}
	fo(j, 1, p[0]) for(int i = 1; i <= m && (ll) p[j] * p[j] <= w[i]; i ++) {
		int k = (w[i] / p[j] <= sqr) ? i1[w[i] / p[j]] : i2[n / (w[i] / p[j])];
		g[i] = ((ll) g[i] - (ll) p[j] * (g[k] - sp[j - 1]) % mo + mo) % mo;
		h[i] = (h[i] - h[k] + (j - 1) + mo) % mo;
	}
	printf("%d\n", (dg(n, 1) + 1) % mo);
}

递推版:

更改 s s s的定义,类似于之前的 g g g
s ( n , j ) s(n,j) s(n,j)表示2-n的数中,要么是质数,要么最小质因子大于等于 p j p_j pj的数 x x x ∑ f ( x ) \sum f(x) f(x)
s ( n , j ) = s ( n , j + 1 ) + ∑ e ∣ e > = 1 和 p j e + 1 < = n   f ( p j e ) ∗ s ( n / p j e ) + f ( p j e + 1 ) s(n,j)=s(n,j+1)+\sum_{e|e>=1和p_j^{e+1}<=n}~f(p_j^e)*s(n/p_j^e)+f(p_j^{e+1}) s(n,j)=s(n,j+1)+ee>=1pje+1<=n f(pje)s(n/pje)+f(pje+1)
初值 s ( n , p 0 + 1 ) = g ( n ) s(n,p0+1)=g(n) s(n,p0+1)=g(n),写法类似于g,就是先枚举j,然后再转移。

对比:

根据LL实测,递归版在求一个的时候会比递推版快,因为有些状态走不到。

但是如果对于所有的 ⌊ n i ⌋ \lfloor {n \over i} \rfloor in都要求前缀和,就只能用递推版了。

你可能感兴趣的:(模版,筛,数论杂集)