Min_26 筛学习笔记
Register_int · · 题解
前言
好像没啥要说的,就是没人写想写一写
Min_26 筛
当积性函数
先来约定一些记号:
-
-
-
-
-
- 除法或开根号若无说明均为向下取整。
-
该筛法共分为四步,总复杂度为
Step 1
求出
来看看 Min25 筛是怎么处理的。由于
减去的部分是「最小质因子恰好为
如果直接计算
考虑 根 号 分 治。是的你没听错,根号分治。
首先对于
看起来好像和原来完全没区别啊,还把好不容易搞出来的 dp 拆回去了。但实际上我们可以枚举
但是复杂度依然爆炸。没事,对于
第一步可能把你震撼了三次,但后面就好很多了。
Step 2
求出
因为能造成贡献的
容易发现,当
Step 3
依次求出
可以发现前后区别就是
直接暴力转移复杂度爆炸,仍然考虑根号分治:对于
Step 4
依次求出
这里直接用上一步的式子暴力做就可以了。时间复杂度
参考代码(P5325 【模板】Min_25 筛)
有
没有特意卡常的情况下跑出了总和 1.57s 的好成绩。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 10;
const int mod = 1e9 + 7;
const int inv = (mod + 1) / 6;
inline
ll qpow(ll b, ll p) {
ll res = 1;
for (; p; p >>= 1, b = b * b % mod) if (p & 1) res = res * b % mod;
return res;
}
int p[MAXN], tot, t3 = 1, t6 = 1; bool vis[MAXN];
ll s[MAXN][3], n2, n3, n6, n23; // sqrt(n), cbrt(n), sqrt[6](n), n^2/3
inline
void init(int n) {
vis[1] = 1, n += 10;
for (int i = 1; i <= n; i++) {
if (!vis[i]) p[++tot] = i;
for (int j = 1; j <= tot; j++) {
if (i * p[j] > n) break;
vis[i * p[j]] = 1;
if (i % p[j] == 0) break;
}
if (i == n3) t3 += tot;
if (i == n6) t6 += tot;
}
for (int i = 1; i <= tot; i++) {
s[i][1] = (s[i][1] + p[i]) % mod;
s[i][2] = (s[i][2] + (ll)p[i] * p[i] % mod) % mod;
}
}
inline
ll calc(ll n, int k) {
n %= mod;
if (!k) return n;
if (k == 1) return (n * (n + 1) >> 1) % mod;
if (k == 2) return n * (n + 1) % mod * (2 * n + 1) % mod * inv % mod;
}
inline
ll f(ll n) {
n %= mod;
return (n * n - n) % mod;
}
ll F[MAXN << 1], g[MAXN << 1][3], val[MAXN << 1]; // F(n / i)
ll n; int m;
inline
ll get(ll k) {
return k <= n2 ? k : m - n / k + 1;
}
ll c[MAXN];
inline
void add(int k, ll x) {
for (int i = k; i <= m; i += i & -i) c[i] = (c[i] + x) % mod;
}
inline
ll ask(int k) {
ll res = 0;
for (int i = k; i; i &= i - 1) res = (res + c[i]) % mod;
return res;
}
void dfs(int k, int tp, ll v, ll fv) { // 搜索找 lpf(x)=p_k
if (!~tp || v != p[k]) add(get(n / (n / v)), ~tp ? mod - fv : fv);
for (int i = k + 1; p[i] * v < n23 && i <= tot; i++) {
for (ll x = p[i]; v * x < n23; x *= p[i]) {
dfs(i, tp, v * x, fv * (~tp ? qpow(x, tp) : f(x)) % mod);
}
}
}
inline
void upd_lpf(int k, int tp) { // 搜索入口
for (ll i = p[k]; i < n23; i *= p[k]) {
dfs(k, tp, i, ~tp ? qpow(i, tp) : f(i));
}
}
int main() {
// 初始化
scanf("%lld", &n);
n2 = sqrtl(n), n3 = cbrtl(n), n6 = pow(n, 1.l / 6);
init(n2), n23 = n / n3;
for (ll i = 1; i < n; i = n / (n / (i + 1))) val[++m] = i; val[++m] = n;
// step 1
for (int i = 1; i <= m; i++) g[i][1] = calc(val[i], 1) - 1, g[i][2] = calc(val[i], 2) - 1;
// 1. 对 lpf(x) <= sqrt[6](n) 暴力。
for (int i = 1; i < t6; i++) {
for (int j = m, x; j && p[i] <= val[j] / p[i]; j--) {
x = get(val[j] / p[i]);
g[j][1] = (g[j][1] - p[i] * (g[x][1] - g[p[i - 1]][1]) % mod + mod) % mod;
g[j][2] = (g[j][2] - p[i] * p[i] % mod * (g[x][2] - g[p[i - 1]][2]) % mod + mod) % mod;
}
}
for (int k = 1; k <= 2; k++) { // 枚举次数
for (int i = 1; val[i] < n23; i++) c[i] = (g[i][k] - g[i & i - 1][k] + mod) % mod;
// 2. 对 x <= n^2/3 使用树状数组优化原来的递推。
for (int i = t6; i < t3; i++) {
for (int j = m; j && val[j] >= n23 && p[i] <= val[j] / p[i]; j--) {
ll x = val[j] / p[i];
if (x < n23) g[j][k] = (g[j][k] - qpow(p[i], k) * (ask(get(x)) - ask(p[i - 1])) % mod + mod) % mod;
else g[j][k] = (g[j][k] - qpow(p[i], k) * (g[get(x)][k] - ask(p[i - 1])) % mod + mod) % mod;
}
upd_lpf(i, k);
}
for (int i = 1; i <= m && val[i] < n23; i++) g[i][k] = ask(i);
// 3. 其余暴力。
for (int i = t3; i <= tot; i++) {
for (int j = m; j && val[j] >= n23 && p[i] <= val[j] / p[i]; j--) {
g[j][k] = (g[j][k] - qpow(p[i], k) * (g[get(val[j] / p[i])][k] - ask(p[i - 1])) % mod + mod) % mod;
}
}
}
for (int i = 1; i <= m; i++) F[i] = (g[i][2] - g[i][1] + mod) % mod;
// step 2
for (int i = m; i; i--) {
if (val[i] < p[t3]) { F[i] = 1; continue; }
F[i] = (F[i] + 1 - F[p[t3] - 1] + mod) % mod;
for (int j = t3; j <= tot && p[j] <= val[i] / p[j]; j++) {
ll x = f(p[j]) * (F[get(val[i] / p[j])] - F[p[j]] + mod) % mod;
F[i] = (F[i] + f((ll)p[j] * p[j] % mod) + x) % mod;
}
}
// step 3
for (int i = 1; i <= m && val[i] < n23; i++) c[i] = (F[i] - F[i & i - 1] + mod) % mod;
for (int i = t3 - 1; i >= t6; i--) {
for (int j = m; j && val[j] >= n23; j--) {
for (ll x = p[i]; x <= val[j]; x *= p[i]) {
if (val[j] / x >= n23) F[j] = (F[j] + F[get(val[j] / x)] * f(x) % mod) % mod;
else F[j] = (F[j] + ask(get(val[j] / x)) * f(x) % mod) % mod;
}
}
upd_lpf(i, -1);
}
for (int i = 1; i <= m && val[i] < n23; i++) F[i] = ask(i);
// step 4
for (int i = t6 - 1; i; i--) {
for (int j = m; j; j--) {
for (ll x = p[i]; x <= val[j]; x *= p[i]) {
F[j] = (F[j] + F[get(val[j] / x)] * f(x) % mod) % mod;
}
}
}
printf("%lld\n", F[m]);
}
参考资料
- dengtesla - 新版 min25 筛详解
Min_25 本人已经爆炸的博客