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筛的大体思想是将1n之间的数分为三部分:质数、合数和(第三部分你猜啊)~1

Part1.处理质数

\(\mathrm{prime}(i)\)表示返回\(i\)是否为质数。

对于每个\(\lfloor\frac n x\rfloor\),我们先预处理出

\[\sum_{i=2}^{\lfloor\frac n x\rfloor}[\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)\or\mathrm{minp}(i)>p_b]i^k \]

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

\[\sum_{i=2}^{\lfloor\frac n x\rfloor}[\mathrm{prime}(i)]i^k=g(\lfloor\frac n x\rfloor,|P|) \]

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

\[g(a,b)=\begin{cases}g(a,b-1)&a<p_b^2\\g(a,b-1)-p_b^k\left(g(\lfloor\frac a {p_b}\rfloor,b-1)-g(p_{b-1},b-1)\right)&a\ge p_b^2\end{cases} \]

什么意思呢??

  • \(a<p_b^2\),所有\(a\)以内的合数已经被筛掉了,所以不影响,直接转移即可。

  • \(a\ge p_b^2\),我们考虑在第\(b\)轮筛法中筛掉了哪些数。

    每一个\(\le\lfloor\frac a{p_b}\rfloor\)的且不含有\(\le p_b\)的质因子的数的\(p_b\)倍都会被筛掉

    考虑减去\(g(\lfloor\frac a{p_b}\rfloor,b-1)\),但由于这之中包含了\(<p_b\)质数,我们在加上\(g(p_{b-1},b-1)\)加回来即可。

    由于\(p_{b-1}<\sqrt n\),所以合法的\(\lfloor\frac n x\rfloor\)中一定存在\(p_{b-1}\)

由于最后我们只需要所有\(\lfloor\frac n x \rfloor\)\(g\)值来作为答案,并且在递推过程中只会用到这些值 所以只需要记录\(O(\sqrt n)\)\(\lfloor \frac n x\rfloor\)递推即可

即对于每个\(\lfloor\frac n x\rfloor\),我们求出了\(\sum_{i=2}^{\lfloor\frac n x\rfloor}[\mathrm{prime}(i)]f(i)=\sum_kg(\lfloor\frac n x \rfloor,|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_b\and\mathrm{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(\lfloor\frac a {p_t^i}\rfloor,i+1)*f(p_i^t)+f(p_i^{t+1})\right) \]

    注意后面加上的那玩意儿是因为\(S\)中1不被考虑在内。

    所以:

    \[S(a,b)=\begin{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(\lfloor\frac a {p_t^i}\rfloor,i+1)*f(p_i^t)+f(p_i^{t+1})\right) ,&a>p_b\end{cases} \]

    据说这个直接搜就行了,不加记忆化都跑得飞快-_-||

实现

我们可以预处理所有的\(\lfloor \frac n x\rfloor​\)的值,并从小到大记为\(a_1,\dots,a_m​\)

对于\(1\le i\le \sqrt n\),有\(a_i=i\)

对于其它的\(i\)\(\lfloor\frac n {a_i}\rfloor=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}^n\sigma_0(i^k)\),满足\(n,k\le 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\n", chuans(n, 1) + 1);
	}
	return 0;
}
posted @ 2019-01-18 14:48  ghj1222  阅读(245)  评论(0编辑  收藏  举报