min_25筛是洲阁筛的简化版,虽然我并不会洲阁筛。
min_25筛可以筛一些特殊积性函数的前缀和,有些不是积性函数也可以筛,比如说最大真因子。
同杜教筛一样,同时筛出了所有 ⌊ n i ⌋ \lfloor {n \over i} \rfloor ⌊in⌋的前缀和。
至于min_25能筛的积性函数有哪些要求,在博客后面会讨论
所有时间复杂度证明见朱大佬2018国家预备队论文。
1 − n 1-n 1−n内的所有数的最小质因子都会不大于 n \sqrt n n,同欧拉筛法,我们就是在枚举到一个数的最小质因子的时去统计到它。
洲阁筛的第一步就是求出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 2−n的数中,要么是质数,要么最小质因子大于 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,j−1)g(n,j−1)−f(pj)[g(pjn,j−1)−∑i=1j−1f(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)=n−1,其它题中,可能是和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)
知道了 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=1j−1f(pi)+∑k=jp0∑e∣e>=1和pke+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)+∑e∣e>=1和pje+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⌋都要求前缀和,就只能用递推版了。