引入问题:求一个积性函数(f(i))的前缀和
其中(f(i))满足对质数(p)有(f(p))是关于(p)的低次多项式、(f(p^c))的值可以快速求出。
Min_25筛时间复杂度为(O(frac{n^frac{3}{4}}{log n})),空间复杂度为(O(sqrt n)),实现常数较小。
Min_25筛的大体思想是将1~n之间的数分为三部分:质数、合数和(第三部分你猜啊)1
Part1.处理质数
设(mathrm{prime}(i))表示返回(i)是否为质数。
对于每个(lfloorfrac n x floor),我们先预处理出
由于对于质数(p),(f(p))可以被表示为若干个(p^k)的和,所以我们可以把多项式每一项分别求值再相加,所以我们现在只考虑(f(p)=p^k)的情况。注意这里(f)是完全积性函数。
设(mathrm{minp}(i))表示(i)质因数分解后最小质因子,(p_i)代表从小到大第(i)个质数。设
直观地说,(g(a,b))就是做(b)轮埃氏筛法后(2)~(a)中剩下的所有数的(k)次幂的和。设(|P|)为(2)~(sqrt n)内素数集合的大小,则有
从小到大枚举质数,模拟埃氏筛法的过程,从(g(?,b-1))推导(g(?,b)),不难得到
什么意思呢??
-
若(a<p_b^2),所有(a)以内的合数已经被筛掉了,所以不影响,直接转移即可。
-
若(age p_b^2),我们考虑在第(b)轮筛法中筛掉了哪些数。
每一个(lelfloorfrac a{p_b} floor)的且不含有(le p_b)的质因子的数的(p_b)倍都会被筛掉
考虑减去(g(lfloorfrac a{p_b} floor,b-1)),但由于这之中包含了(<p_b)质数,我们在加上(g(p_{b-1},b-1))加回来即可。
由于(p_{b-1}<sqrt n),所以合法的(lfloorfrac n x floor)中一定存在(p_{b-1})
由于最后我们只需要所有(lfloorfrac n x floor)的(g)值来作为答案,并且在递推过程中只会用到这些值 所以只需要记录(O(sqrt n))个(lfloor frac n x floor)递推即可
即对于每个(lfloorfrac n x floor),我们求出了(sum_{i=2}^{lfloorfrac n x floor}[mathrm{prime}(i)]f(i)=sum_kg(lfloorfrac n x floor,|P|)),(|P|)代表(sqrt n)以内的素数集合的大小。
Part2.计算答案
令
我们分为两部分处理。
-
计算(S(a,b))中所有质数的贡献,即(sum_{i=2}^n[mathrm{minp}(i)ge p_bandmathrm{prime}(i)]f(i))
这部分可通过处理的(g)快速得到:即为(g(a,|P|)-g(p_{b-1},|P|))。
-
计算(S(a,b))中所有合数的贡献
我们枚举最小质因子(p_i)和出现次数(t)
[sum_{i=b}^{|P|}sum_{p_i^{t+1}le a}left(S(lfloorfrac a {p_t^i} floor,i+1)*f(p_i^t)+f(p_i^{t+1}) ight) ]注意后面加上的那玩意儿是因为(S)中1不被考虑在内。
所以:
[S(a,b)=egin{cases} 0,&a<p_b\ displaystyle g(a,|P|)-g(p_{b-1},|P|)+sum_{i=b}^{|P|}sum_{p_i^{t+1}le a}left(S(lfloorfrac a {p_t^i} floor,i+1)*f(p_i^t)+f(p_i^{t+1}) ight) ,&a>p_bend{cases} ]据说这个直接搜就行了,不加记忆化都跑得飞快-_-||
实现
我们可以预处理所有的(lfloor frac n x floor)的值,并从小到大记为(a_1,dots,a_m)
对于(1le ile sqrt n),有(a_i=i)
对于其它的(i)有(lfloorfrac n {a_i} floor=m-i+1)
这样我们可以实现(O(1))从数字到下标的变换
在处理(g)数组我们直接用(a)数组中的下标存储,空间复杂度即(O(sqrt n))。
转移(g)的时候只需要从大到小枚举(就像01背包那样,这样直接在原数组转移,滚都不用滚了),对于(a< p_b^2)的那部分不用搭理。。。时间复杂度是(O(frac{n^{frac 3 4}}{log n}))的,不会证。
转移(S)的时候直接枚举,递归就行(雷哥说是(O((n^2)^{n^2}))的复杂度),复杂度也是(O(frac{n^{frac 3 4}}{log n})),也不会证。
例子
SPOJ-DIVCNTK
给定(n,k),设(sigma_0(x))表示(x)的约数个数,求(sum_{i=1}^nsigma_0(i^k)),满足(n,kle 10^{10}),答案ull自然溢出。
//SPJ DIVCNTK
#include <cstdio>
#include <cmath>
using namespace std;
long long n, sqt, k;
long long a[2000010], g[2000010];
int prime[1000010];
int m, tot;
int getid(long long x) { return x <= sqt ? x : m - n / x + 1; }
unsigned long long chuans(long long n, int b)
{
if (n < prime[b]) return 0;
unsigned long long ans = (k + 1) * (g[getid(n)] - g[getid(prime[b - 1])]);
for (int i = b; i <= tot && prime[i] * (long long)prime[i] <= n; i++)
for (long long x = prime[i], f = k + 1; x * prime[i] <= n; x *= prime[i], f += k)
ans += (chuans(n / x, i + 1) + 1) * f + k;
return ans;
}
int main()
{
int t;
scanf("%d", &t);
while (t --> 0)
{
scanf("%lld%lld", &n, &k);
sqt = sqrt(n);
tot = m = 0;
for (long long i = 1; i <= n; i = a[m] + 1)
a[++m] = n / (n / i), g[m] = a[m] - 1;
for (int i = 2; i <= sqt; i++)
if (g[i] != g[i - 1])
{
prime[++tot] = i;
long long sqr = i * (long long)i;
for (int j = m; a[j] >= sqr; j--)
g[j] -= g[getid(a[j] / i)] - g[i - 1];
}
printf("%llu
", chuans(n, 1) + 1);
}
return 0;
}