Min_25筛学习笔记

引入问题:求一个积性函数(f(i))的前缀和

[sum_{i=1}^nf(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),我们先预处理出

[sum_{i=2}^{lfloorfrac n x floor}[mathrm{prime}(i)]f(i) ]

由于对于质数(p)(f(p))可以被表示为若干个(p^k)的和,所以我们可以把多项式每一项分别求值再相加,所以我们现在只考虑(f(p)=p^k)的情况。注意这里(f)是完全积性函数。

(mathrm{minp}(i))表示(i)质因数分解后最小质因子,(p_i)代表从小到大第(i)个质数。设

[g(a,b)=sum_{i=2}^a[mathrm{prime}(i)ormathrm{minp}(i)>p_b]i^k ]

直观地说,(g(a,b))就是做(b)轮埃氏筛法后(2)~(a)中剩下的所有数的(k)次幂的和。设(|P|)(2)~(sqrt n)内素数集合的大小,则有

[sum_{i=2}^{lfloorfrac n x floor}[mathrm{prime}(i)]i^k=g(lfloorfrac n x floor,|P|) ]

从小到大枚举质数,模拟埃氏筛法的过程,从(g(?,b-1))推导(g(?,b)),不难得到

[g(a,b)=egin{cases}g(a,b-1)&a<p_b^2\g(a,b-1)-p_b^kleft(g(lfloorfrac a {p_b} floor,b-1)-g(p_{b-1},b-1) ight)&age p_b^2end{cases} ]

什么意思呢??

  • (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}^a[mathrm{minp}(i)ge p_b]f(i) ]

我们分为两部分处理。

  1. 计算(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|))

  2. 计算(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;
}
原文地址:https://www.cnblogs.com/oier/p/10287574.html