算法学习笔记:Min_25 筛

一些记号

下文中令:

  • \(\operatorname{lpf}(x)=\min_{p\mid x}p\),即 \(x\) 的最小质因子.
  • \(p_k\) 表示第 \(k\) 小的质数。
  • \(\pi(n)\) 表示 \(n\) 以内的质数个数。

Min_25 筛

Min_25 筛采用素数拟合的思路,我们分别考虑质数和合数处的 \(f\) 值之和。先来考虑

\[\sum_{i=1}^n[i\in\mathbb{P}]f(i) \]

我们构造完全积性函数 \(F\),使得其在质数处的取值和 \(f\) 相等。那上式显然变成了

\[\sum_{i=1}^n[i\in\mathbb{P}]F(i) \]

\(n\) 很大,我们不可能筛出所有 \(n\) 以内的质数。不妨考虑容斥,我们用 \(\sum_{i=1}^nF(i)\) 减去所有合数处的 \(F\) 的值,而所有合数 \(x\) 必然满足 \(\operatorname{lpf}(x)\leq\lfloor\sqrt{x}\rfloor\),所以我们考虑类似埃氏筛的思路,从小到大考虑每个 \(\lfloor\sqrt{n}\rfloor\) 以内的质数 \(p\),计算它们减掉的贡献。

由此考虑 DP。令

\[g(n,x)=\sum_{i=1}^n[i\in\mathbb{P}\lor\operatorname{lpf}(i)>p_x]F(i) \]

考虑如何从 \(g(n,x-1)\) 转移到 \(g(n,x)\),显然此时限制条件变强。不难发现去掉的恰好是那些 \(\operatorname{lpf}\)\(p_x\) 的数,即

\[\Delta=g(n,x-1)-g(n,x)=\sum_{i=1}^{n}[\operatorname{lpf}(i)=p_x]F(i) \]

尝试刻画 \(\operatorname{lpf}(i)=p_x\) 的条件。注意到 \(F\) 是完全积性函数,我们可以提一个 \(F(p_x)\) 出来,这样子剩余部分应该满足 \(\operatorname{lpf}\geq p_x\)。考察 \(g\left(\left\lfloor\frac{n}{p_x}\right\rfloor,x-1\right)\),我们发现这仅仅多算了那些 \(<p_x\) 的质数处的 \(F\) 值之和。由于我们只考虑 \(\leq\lfloor\sqrt{n}\rfloor\) 的质数,所以不难预处理出 \(sp_x=\sum_{i=1}^xF(p_i)\)。于是我们得到了递推式:

\[g(n,x)=g(n,x-1)-F(p_x)\left(g\left(\left\lfloor\frac{n}{p_x}\right\rfloor,x-1\right)-sp_{x-1}\right) \]

根据杜教筛那一套理论,我们只需要 \(g\left(\left\lfloor\frac{n}{i}\right\rfloor,x\right)(p_x^2\leq \left\lfloor\frac{n}{i}\right\rfloor)\) 处的值,可以证明对这些点值 DP,时间复杂度是 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}\right)\) 的。

实现时,我们需要给 \(\left\lfloor\frac{n}{i}\right\rfloor\) 编号。这个挺典的,我们开两个 \(\mathcal{O}(\sqrt{n})\) 的索引数组 \(id_1\)\(id_2\)。若 \(\left\lfloor\frac{n}{i}\right\rfloor\leq\sqrt{n}\),则将编号存在 \(id_1,\left\lfloor\frac{n}{i}\right\rfloor\) 中,否则令 \(j=\left\lfloor\frac{n}{\left\lfloor\frac{n}{i}\right\rfloor}\right\rfloor\),将编号存在 \(id_{2,j}\) 中即可。

注意我们最终只需要用到 \(g(n,\pi(\lfloor\sqrt{n}\rfloor))=\sum_{i=1}^n[i\in\mathbb{P}]F(i)\),因此 \(g\) 的第二维可以用滚动数组去掉。

回到原题,我们类似地设计 DP,令

\[S(n,x)=\sum_{i=1}^n[\operatorname{lpf}(i)>p_x]f(i) \]

还是分成质数和合数两部分考虑。对于前者,不难得出其贡献为

\[g(n,\pi(\lfloor\sqrt{n}\rfloor))-sp_x \]

由于不保证 \(f\) 是完全积性函数,我们不能只提单个质因子出来,需要一次性全部提出。也就是我们先枚举 \(p_i(i>x)\),再枚举其指数 \(c\),这样就可以把 \(f(p_i^c)\) 提出来了。剩余部分应该满足 \(\operatorname{lpf}>p_i\),也就是 \(S\left(\left\lfloor\frac{n}{p_i^c}\right\rfloor,i\right)\),不过由于我们不把 \(1\) 纳入 \(S,g\) 的计算范围内,所以这样会漏掉 \(f(p_i^c\times 1)\),当 \(c=1\) 时,\(f(p_i)\) 我们已经在质数的部分计算过了,不用加回去,而当 \(c>1\) 时,我们则需要把 \(f(p_i^c)\) 加回去。由此得到递推式:

\[S(n,x)=g(n,\pi(\lfloor\sqrt{n}\rfloor))-sp_x+\sum_{i=x+1}^{p_i^2\leq n}\sum_{c=1}^{p_i^c\leq n}f(p_i^c)\left(S\left(\left\lfloor\frac{n}{p_i^c}\right\rfloor,i\right)+[c>1]\right) \]

这部分的时间复杂度被证明是 \(\mathcal{O}(n^{1-\varepsilon})\) 的。

最终答案就是 \(S(n,0)+f(1)\)

根据上面的过程,不难发现 Min_25 筛要求 \(f\) 和我们构造出的 \(F\) 具备以下性质:

  • \(F(p)=f(p)\) 可以快速计算,\(F\) 的前缀和可以快速计算。
  • \(f(p^c)\) 可以快速计算。

这些性质实际上是不难满足的。

注意我们实际上可以把 \(f(p)\) 分解成若干个完全积性函数 \(F(p)\) 的线性组合,在计算 \(S(n,x)\) 用到 \(\sum_{i=x+1}^{\pi(\lfloor\sqrt{n}\rfloor)}f(p_i)\) 时,用同样的方式线性组合对应的 \(g\)\(sp\) 的值即可。

Luogu P5325 【模板】Min_25 筛

板子题。本题中 \(f(p)=p^2-p\),构造 \(F_1(p)=p\)\(F_2(p)=p^2\) 即可。

代码:

ll n, sn, num[N << 1];
int sz, pr[N / 10], sp1[N / 10], sp2[N / 10];
int cnt, id1[N], id2[N];
int g1[N << 1], g2[N << 1];
bool v[N];

inline void sieve(int n) {
    for (int i = 2; i <= n; ++i) {
        if (!v[i]) {
            pr[++sz] = i;
            sp1[sz] = add(sp1[sz - 1], i);
            sp2[sz] = add(sp2[sz - 1], 1ll * i * i % MOD);
        }
        for (int j = 1; j <= sz && i * pr[j] <= n; ++j) {
            v[i * pr[j]] = 1;
            if (i % pr[j] == 0) break;
        }
    }
}
inline int calc(ll x, int y) {
    if (pr[y] >= x) return 0;
    int k = x <= sn ? id1[x] : id2[n / x];
    int ans = sub(sub(g2[k], g1[k]), sub(sp2[y], sp1[y]));
    for (int i = y + 1; i <= sz && 1ll * pr[i] * pr[i] <= x; ++i)
        for (ll j = pr[i], c = 1; j <= x; j *= pr[i], ++c) {
            ll j2 = j % MOD;
            cadd(ans, j2 * (j2 - 1) % MOD * (calc(x / j, i) + (c > 1)) % MOD);
        }
    return ans;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n, sieve(sn = sqrt(n));
    for (ll l = 1, r, x; l <= n; l = r + 1) {
        num[++cnt] = x = n / l, r = n / x;
        ll x2 = x % MOD;
        g1[cnt] = sub(((x2 * (x2 + 1)) >> 1) % MOD, 1);
        g2[cnt] = sub(x2 * (x2 + 1) % MOD * (x2 * 2 % MOD + 1) % MOD * inv6 % MOD, 1);
        if (x <= sn) id1[x] = cnt;
        else id2[n / x] = cnt;
    }
    for (int i = 1; i <= sz; ++i)
        for (int j = 1; j <= cnt && 1ll * pr[i] * pr[i] <= num[j]; ++j) {
            int k = (num[j] / pr[i]) <= sn ? id1[num[j] / pr[i]] : id2[n / (num[j] / pr[i])];
            csub(g1[j], 1ll * pr[i] * sub(g1[k], sp1[i - 1]) % MOD);
            csub(g2[j], 1ll * pr[i] * pr[i] % MOD * sub(g2[k], sp2[i - 1]) % MOD);
        }
    cout << add(calc(n, 0), 1);
    return 0;
}

SPOJ 34096 DIVCNTK - Counting Divisors (general)

不难看出 \(f(n)=d(n^k)\) 是积性函数,且 \(f(p)=k+1\)\(f(p^c)=ck+1\),上 Min_25 筛,令 \(F=1\) 即可,后面再乘上 \(k+1\)

Luogu P5493 质数前缀统计

用 Min_25 筛的前半部分做 DP,边界 \(g(n,0)\) 是自然数幂和,直接 \(\mathcal{O}(k)\) 拉插即可。时间复杂度 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}+\sqrt{n}k\right)\)

LOJ 6235 区间素数个数

\(F(p)=1\),用 Min_25 筛前半部分直接 DP 即可。时间复杂度 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}+\sqrt{n}\right)\)

SPOJ 22549 DIVFACT4 - Divisors of factorial (extreme)

容易想到分别考虑每个 \([1,n]\) 中的质数 \(p\),经典地,\(n!\)\(p\) 因子的个数是

\[\sum_{c=1}^{\log_p{n}}\left\lfloor\frac{n}{p^c}\right\rfloor \]

因此

\[d(n!)=\prod_{i=1}^{\pi(n)}\left(1+\sum_{c=1}^{\log_{p_i}{n}}\left\lfloor\frac{n}{p_i^c}\right\rfloor\right) \]

注意到对于 \(>\sqrt{n}\)\(p\),它们都只有 \(1+\left\lfloor\frac{n}{p}\right\rfloor\) 的贡献,可以整除分块,这样对于一个块 \([l,r]\),计算出 \([l,r]\) 内的质数个数 \(cnt\),给答案乘上 \(\left(1+\left\lfloor\frac{n}{l}\right\rfloor\right)^{cnt}\) 即可。我们询问的端点都是形如 \(\left\lfloor\frac{n}{i}\right\rfloor\) 的位置,用 Min_25 筛的前半部分即可解决。这部分时间复杂度为 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}+\sqrt{n}\log{n}\right)\)

而对于 \(\leq\sqrt{n}\)\(p\),我们可以 \(\mathcal{O}(\log{n})\) 暴力跳,共有 \(\pi\left(\lfloor\sqrt{n}\rfloor\right)=\mathcal{O}\left(\frac{\sqrt{n}}{\log{n}}\right)\) 个这样的 \(p\),时间复杂度为 \(\mathcal{O}(\sqrt{n})\)

由于模数 \(m\) 很大,需要上快速乘。

总时间复杂度为 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}+\sqrt{n}\log{n}\right)\)

代码:

inline ll qmul(ll a, ll b) {
    ull res = 1ull * a * b - (ull)((long double)a / m * b + 0.5L) * m;
    return res < m ? res : res + m;
}
inline ll qpow(ll a, ll b) {
    a %= m;
    ll res = 1;
    for (; b; b >>= 1) {
        if (b & 1) res = qmul(res, a);
        a = qmul(a, a);
    }
    return res;
}
inline void sieve(int n) {
    v[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!v[i]) v[i] = pr[++sz] = i;
        for (int j = 1; j <= sz && i * pr[j] <= n; ++j) {
            int x = pr[j];
            v[i * x] = x;
            if (v[i] == x) break;
        }
    }
}
inline int get_id(ll x) { return x <= sn ? id1[x] : id2[n / x]; }
inline ll calc_cnt(ll n, int p) {
    ll res = 0;
    for (; n >= p; n /= p) cadd(res, (n / p) % m);
    return res;
}

int main() {
    ios::sync_with_stdio(0), cin.tie(0);
    sieve(N - 5);
    cin >> t;
    while (t--) {
        cin >> n >> m, sn = sqrt(n), cnt = 0, ans = 1 % m;
        for (ll l = 1, r, x; l <= n; l = r + 1) {
            r = n / (x = n / l);
            num[++cnt] = x, g[cnt] = x - 1;
            if (x <= sn) id1[x] = cnt;
            else id2[r] = cnt;
        }
        for (int i = 1; i <= sz && pr[i] <= sn; ++i)
            for (int j = 1; j <= cnt && 1ll * pr[i] * pr[i] <= num[j]; ++j) {
                int pos = get_id(num[j] / pr[i]);
                g[j] -= g[pos] - (i - 1);
            }
        for (int i = 1; i <= sz && pr[i] <= sn; ++i) ans = qmul(ans, add(calc_cnt(n, pr[i]), 1));
        for (ll l = sn + 1, r, x, c; l <= n; l = r + 1) {
            r = n / (x = n / l);
            ans = qmul(ans, qpow(x + 1, g[get_id(r)] - g[get_id(l - 1)]));
        }
        cout << ans << '\n';
    }
    return 0;
}

SPOJ 19975 APS2 - Amazing Prime Sequence (hard)

对 Min_25 筛不那么裸的运用。

还是分开考虑质数和合数。对于质数,相当于求

\[\sum_{i=1}^n[i\in\mathbb{P}]i \]

这个 Min_25 筛随便做,令 \(F(p)=p\),做前半部分的 DP 即可。

对于合数,我们考察 Min_25 筛前半部分的 DP:

\[g(n,x)=g(n,x-1)-F(p_x)\left(g\left(\left\lfloor\frac{n}{p_x}\right\rfloor,x-1\right)-sp_{x-1}\right) \]

注意到 \(g\left(\left\lfloor\frac{n}{p_x}\right\rfloor,x-1\right)-sp_{x-1}\) 计算的就是 \(n\) 以内 \(\operatorname{lpf}=p_x\) 的数的个数,于是我们在 DP 过程中累加答案即可。

时间复杂度 \(\mathcal{O}\left(\frac{n^{\frac{3}{4}}}{\log{n}}+\sqrt{n}\right)\)

posted @ 2025-05-16 12:23  P2441M  阅读(35)  评论(0)    收藏  举报