Min_25 筛 & Min_26 筛 & zzt 求和法 给(人能看懂的)代码的 学习笔记
看不懂别人博客里写的代码,所以只好自己实现常数超大的版本了,,,
记号:
前置知识:
用整除分块可以轻松求出
L = sqrt(n);
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
}
//此时 v 中从大到小存储 n 的块筛
int O(int x) { return x <= L ? o1[x] : o2[n / x]; } //返回 x 是从大到小第几个块筛
给定数论函数
Part I 求前缀质数处的和
即求
Min_25 筛为啥叫 ex埃筛 呢?
上面提到,
于是对于少项式的每一项,我们只需要求
模拟埃筛的过程,设
显然有初始值
埃筛只用筛
对于
Min_25
考虑算
筛完
第
考虑后面的
枚举
考虑后面的
可以发现,
依次回代,可得
这里的
实现时 double
),然后要算
给出洛谷 P5493 的代码:
#include <cmath>
#include <cstdio>
#define int long long
bool b[10000050];
double d[10000050];
int n, k, M, L, c, _, Z, _f[20], _o[20], _v[20], v[10000050], o1[10000050], o2[10000050], g[10000050], p[10000050], s[10000050];
int P(int x, int y)
{
int q = 1;
for (x %= M; y; y >>= 1, x = x * x % M)
if (y & 1)
q = q * x % M;
return q;
}
int Q(int n) //拉插求 2^k+...+n^k
{
if (n <= k + 2)
return _f[n];
else
{
static int _l[20], _r[20];
int q = 0, _z = 1;
_l[0] = _r[k + 3] = 1;
for (int i = 1; i <= k + 2; ++i)
_l[i] = _l[i - 1] * ((n - i) % M) % M;
for (int i = k + 2; i >= 1; --i)
_r[i] = _r[i + 1] * ((n - i) % M) % M;
for (int i = 1; i <= k + 2; ++i)
q = (q + _f[i] * _l[i - 1] % M * _r[i + 1] % M * _v[i - 1] % M * _v[k + 2 - i] % M * (k + 2 - i & 1 ? M - 1 : 1)) % M;
return q;
}
}
int O(int x) { return x <= L ? o1[x] : o2[n / x]; } //返回 x 是从大到小第几个块筛
signed main()
{
scanf("%lld%lld%lld", &n, &k, &M);
for (int i = _o[0] = 1; i <= 15; ++i)
_o[i] = _o[i - 1] * i % M;
_v[15] = P(_o[15], M - 2);
for (int i = 14; i >= 0; --i)
_v[i] = _v[i + 1] * (i + 1) % M;
for (int i = 2; i <= k + 2; ++i)
_f[i] = (_f[i - 1] + P(i, k)) % M;
L = sqrt(n), d[1] = 1;
for (int i = 2; i <= L; ++i)
{
if (!b[i])
p[++c] = i, s[c] = (s[c - 1] + P(i, k)) % M; //记得预处理 s
for (int j = 1; i * p[j] <= L; ++j)
{
b[i * p[j]] = 1;
if (!(i % p[j]))
break;
}
d[i] = 1.0 / i; //还有预处理 d
}
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
g[_] = Q(n / l); //g[0][n/l]=2^k+...+(n/l)^k, n/l 的编号为 _ 所以存在 g[_] 处
}
//此时 v 中从大到小存储 n 的块筛
for (int j = 1; j <= c; ++j)
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(v[i] * d[p[j]] + 1e-6)] + M - s[j - 1]) % M) % M;
// g[i] 即为博客中的 g[j][v[i]], v[i] 的编号为 i 所以存在 g[i] 处
// 根据博客,g[j][v[i]]=g[j+1][v[i]]-p[j]^k*(g[j+1][v[i]/p[j]]-s[j-1]), 可以在上面的代码中一一对应
for (int i = 1; i <= L; ++i)
Z = (Z + g[O(n * d[i] + 1e-6)] * i % M * i) % M;
printf("%lld", Z);
return 0;
}
经测试,这份代码 10s 内能跑到
Min_26
考虑 根 号 分 治。
设置阈值
对
诶等等啊,本来每次转移是
所以我们不能对每个
但我们可以爆搜所有
?那你复杂度不是没变吗
所以更新所有
分析一下复杂度,首先每个数只会被爆搜到一次,所以树状数组上的修改只有
对于
总复杂度
……这是不是比原来还菜了?
继续根号分治,求
此时我们只需对
于是树状数组上的修改贡献的复杂度为
总复杂度
给出洛谷 P5493 的代码:
#include <cmath>
#include <cstdio>
#define int long long
bool b[10000050];
double d[10000050];
int n, k, M, L, c, _, Z, n6, n3, n23, t6, t3, _f[20], _o[20], _v[20], v[10000050], o1[10000050], o2[10000050], g[10000050], p[10000050], s[10000050], C[10000050];
int P(int x, int y)
{
int q = 1;
for (x %= M; y; y >>= 1, x = x * x % M)
if (y & 1)
q = q * x % M;
return q;
}
int Q(int n) //拉插求 2^k+...+n^k
{
if (n <= k + 2)
return _f[n];
else
{
static int _l[20], _r[20];
int q = 0, _z = 1;
_l[0] = _r[k + 3] = 1;
for (int i = 1; i <= k + 2; ++i)
_l[i] = _l[i - 1] * ((n - i) % M) % M;
for (int i = k + 2; i >= 1; --i)
_r[i] = _r[i + 1] * ((n - i) % M) % M;
for (int i = 1; i <= k + 2; ++i)
q = (q + _f[i] * _l[i - 1] % M * _r[i + 1] % M * _v[i - 1] % M * _v[k + 2 - i] % M * (k + 2 - i & 1 ? M - 1 : 1)) % M;
return q;
}
}
int O(int x) { return x <= L ? o1[x] : o2[n / x]; } //返回 x 是从大到小第几个块筛
//块筛是从大到小存的,所以对 >=i 的块筛减去一个数是前缀减
//后缀差分一下,前缀减单点求值变为单点减后缀求和
void G(int x, int k)
{
for (; x; x &= x - 1)
C[x] = (C[x] + k) % M;
}
int W(int x)
{
int q = 0;
for (; x <= _; x += x & -x)
q = (q + C[x]) % M;
return q;
}
void D(int u, int fu, int l)
{
G(O(n / (n / u)), (M - fu) % M); //找 >=u 的第一个块筛,设这个块筛是 n/o,则 o<=n/u,这个块筛为 n/(n/u),
//>=u 的所有块筛,即这个块筛及其之前的块筛,g 值都要减去 fu
for (int i = l; i <= c && u * p[i] < n23; ++i)
for (int j = p[i], fj = 1; u * j < n23; j *= p[i])
fj = fj * (s[i] + M - s[i - 1]) % M, D(u * j, fu * fj % M, i + 1);
}
void U(int u) //爆搜 lpf=p[u] 的数,注意 p[u] 本身不应该被筛掉,所以要做一次单点加把之后的单点减抵消掉
{
G(O(n / (n / p[u])), (s[u] + M - s[u - 1]) % M);
for (int i = p[u], fi = 1; i < n23; i *= p[u])
fi = fi * (s[u] + M - s[u - 1]) % M, D(i, fi, u + 1);
}
signed main()
{
scanf("%lld%lld%lld", &n, &k, &M);
L = sqrt(n), n6 = pow(n, 1.0 / 6), n23 = pow(n, 2.0 / 3), n3 = sqrt(n23);
//注意这里需要保证用 n3 内的质数可以筛完 n23,即要保证 n3=sqrt(n23),所以写 n3=pow(n,1.0/3) 会有问题
for (int i = _o[0] = 1; i <= 15; ++i)
_o[i] = _o[i - 1] * i % M;
_v[15] = P(_o[15], M - 2);
for (int i = 14; i >= 0; --i)
_v[i] = _v[i + 1] * (i + 1) % M;
for (int i = 2; i <= k + 2; ++i)
_f[i] = (_f[i - 1] + P(i, k)) % M;
d[1] = t6 = t3 = 1; //t6,t3 表示 n6,n3 内最大质数的编号+1(我下面的循环是左闭右开的,所以要+1)
for (int i = 2; i <= L; ++i)
{
if (!b[i])
p[++c] = i, s[c] = (s[c - 1] + P(i, k)) % M; //记得预处理 s
for (int j = 1; i * p[j] <= L; ++j)
{
b[i * p[j]] = 1;
if (!(i % p[j]))
break;
}
if (i == n6)
t6 += c;
if (i == n3)
t3 += c;
d[i] = 1.0 / i; //还有预处理 d
}
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
g[_] = Q(n / l); //g[0][n/l]=2^k+...+(n/l)^k, n/l 的编号为 _ 所以存在 g[_] 处
}
//此时 v 中从大到小存储 n 的块筛
for (int j = 1; j < t6; ++j) //对于 p[j]<T2 只用转移式而不用树状数组,这里是左闭右开所以 t6 要加一
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(v[i] * d[p[j]] + 1e-6)] + M - s[j - 1]) % M) % M;
for (int i = _; v[i] < n23; --i)
C[i] = (g[i] + M - g[i + (i & -i)]) % M; //O(n) 建树状数组
for (int j = t6; j < t3; ++j) //这里是左闭右开所以 t3 要加一
{
for (int i = 1; n23 <= v[i]; ++i) //v[i]>=T 用转移式求 g[i]
{
int o = v[i] * d[p[j]] + 1e-6;
if (o < n23) //v[i]/p[j]<T 时,g[v[i]/p[j]] 在树状数组上
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (W(O(o)) + M - s[j - 1]) % M) % M;
else //v[i]/p[j]>=T 时,g[v[i]/p[j]] 是用转移式得到的,直接在 g 上查
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(o)] + M - s[j - 1]) % M) % M;
}
U(j); //爆搜 lpf=p[j] 的数,更新 v[i]<T 的 g[i]
}
for (int i = _; v[i] < n23; --i)
g[i] = W(i); //用 sqrt(T) 以内的质数筛完之后,所有 v[i]<T 的 g[i] 不会再变,从树状数组上取出 g 值
for (int j = t3; j <= c; ++j) //对于 p[j]>=sqrt(T) 只有 v[i]>=T 的 g[i] 会受影响,直接用转移式求 g[i]
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(v[i] * d[p[j]] + 1e-6)] + M - s[j - 1]) % M) % M;
for (int i = 1; i <= L; ++i)
Z = (Z + g[O(n * d[i] + 1e-6)] * i % M * i) % M;
printf("%lld", Z);
return 0;
}
经测试,这份代码在 10s 内能跑到
zzt 求和法
能 不 能 再 给 力 点 啊 ?
考虑减少一些树状数组上的查询。
注意到对于
此时不妨把
这样的话,对于
则树状数组上的查询共有
而
树状数组上的修改贡献的复杂度仍为
取
给出洛谷 P5493 的代码:
#include <cmath>
#include <cstdio>
#define int long long
bool b[10000050];
double d[10000050];
int n, k, M, L, c, _, Z, n6, n3, n23, t6, t3, _f[20], _o[20], _v[20], v[10000050], o1[10000050], o2[10000050], g[10000050], p[10000050], s[10000050], C[10000050];
int P(int x, int y)
{
int q = 1;
for (x %= M; y; y >>= 1, x = x * x % M)
if (y & 1)
q = q * x % M;
return q;
}
int Q(int n) //拉插求 2^k+...+n^k
{
if (n <= k + 2)
return _f[n];
else
{
static int _l[20], _r[20];
int q = 0, _z = 1;
_l[0] = _r[k + 3] = 1;
for (int i = 1; i <= k + 2; ++i)
_l[i] = _l[i - 1] * ((n - i) % M) % M;
for (int i = k + 2; i >= 1; --i)
_r[i] = _r[i + 1] * ((n - i) % M) % M;
for (int i = 1; i <= k + 2; ++i)
q = (q + _f[i] * _l[i - 1] % M * _r[i + 1] % M * _v[i - 1] % M * _v[k + 2 - i] % M * (k + 2 - i & 1 ? M - 1 : 1)) % M;
return q;
}
}
int O(int x) { return x <= L ? o1[x] : o2[n / x]; } //返回 x 是从大到小第几个块筛
//块筛是从大到小存的,所以对 >=i 的块筛减去一个数是前缀减
//后缀差分一下,前缀减单点求值变为单点减后缀求和
void G(int x, int k)
{
for (; x; x &= x - 1)
C[x] = (C[x] + k) % M;
}
int W(int x)
{
int q = 0;
for (; x <= _; x += x & -x)
q = (q + C[x]) % M;
return q;
}
void D(int u, int fu, int l)
{
G(O(n / (n / u)), (M - fu) % M); //找 >=u 的第一个块筛,设这个块筛是 n/o,则 o<=n/u,这个块筛为 n/(n/u),
//>=u 的所有块筛,即这个块筛及其之前的块筛,g 值都要减去 fu
for (int i = l; i <= c && u * p[i] < n23; ++i)
for (int j = p[i], fj = 1; u * j < n23; j *= p[i])
fj = fj * (s[i] + M - s[i - 1]) % M, D(u * j, fu * fj % M, i + 1);
}
void U(int u) //爆搜 lpf=p[u] 的数,注意 p[u] 本身不应该被筛掉,所以要做一次单点加把之后的单点减抵消掉
{
G(O(n / (n / p[u])), (s[u] + M - s[u - 1]) % M);
for (int i = p[u], fi = 1; i < n23; i *= p[u])
fi = fi * (s[u] + M - s[u - 1]) % M, D(i, fi, u + 1);
}
signed main()
{
scanf("%lld%lld%lld", &n, &k, &M);
L = sqrt(n), n6 = pow(n, 1.0 / 6), n23 = pow(n / log(n), 2.0 / 3), n3 = sqrt(n23);
//注意这里需要保证用 n3 内的质数可以筛完 n23,即要保证 n3=sqrt(n23),所以写 n3=pow(n,1.0/3) 会有问题
for (int i = _o[0] = 1; i <= 15; ++i)
_o[i] = _o[i - 1] * i % M;
_v[15] = P(_o[15], M - 2);
for (int i = 14; i >= 0; --i)
_v[i] = _v[i + 1] * (i + 1) % M;
for (int i = 2; i <= k + 2; ++i)
_f[i] = (_f[i - 1] + P(i, k)) % M;
d[1] = t6 = t3 = 1; //t6,t3 表示 n6,n3 内最大质数的编号+1(我下面的循环是左闭右开的,所以要+1)
for (int i = 2; i <= L; ++i)
{
if (!b[i])
p[++c] = i, s[c] = (s[c - 1] + P(i, k)) % M; //记得预处理 s
for (int j = 1; i * p[j] <= L; ++j)
{
b[i * p[j]] = 1;
if (!(i % p[j]))
break;
}
if (i == n6)
t6 += c;
if (i == n3)
t3 += c;
d[i] = 1.0 / i; //还有预处理 d
}
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
g[_] = Q(n / l); //g[0][n/l]=2^k+...+(n/l)^k, n/l 的编号为 _ 所以存在 g[_] 处
}
//此时 v 中从大到小存储 n 的块筛
for (int j = 1; j < t6; ++j) //对于 p[j]<T2 只用转移式而不用树状数组,这里是左闭右开所以 t6 要加一
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(v[i] * d[p[j]] + 1e-6)] + M - s[j - 1]) % M) % M;
for (int i = _; v[i] < n23; --i)
C[i] = (g[i] + M - g[i + (i & -i)]) % M; //O(n) 建树状数组
int k = _; //表示当前 i>k 的 g[i] 不会再变,已经存下来
for (int j = t6; j < t3; ++j) //这里是左闭右开所以 t3 要加一
{
for (; v[k] < p[j] * p[j]; --k)
g[k] = W(k); //v[k]<p[j]^2 的 g[k] 不会再变,把它存下来
for (int i = 1; n23 <= v[i]; ++i) //v[i]>=T 用转移式求 g[i]
{
int o = v[i] * d[p[j]] + 1e-6;
if (o < n23 && O(o) <= k) //v[i]/p[j]<T 时,g[v[i]/p[j]] 在树状数组上
//并且 O(o)>k 时 g[O(o)] 的值已经存下来,不用在树状数组上查询
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (W(O(o)) + M - s[j - 1]) % M) % M;
else //v[i]/p[j]>=T 时,g[v[i]/p[j]] 是用转移式得到的,直接在 g 上查
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(o)] + M - s[j - 1]) % M) % M;
}
U(j); //爆搜 lpf=p[j] 的数,更新 v[i]<T 的 g[i]
}
for (; v[k] < n23; --k)
g[k] = W(k); //取出剩下的 v[k]<T 的 g[k] 值
for (int j = t3; j <= c; ++j) //对于 p[j]>=sqrt(T) 只有 v[i]>=T 的 g[i] 会受影响,直接用转移式求 g[i]
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i] = (g[i] + M - (s[j] + M - s[j - 1]) * (g[O(v[i] * d[p[j]] + 1e-6)] + M - s[j - 1]) % M) % M;
for (int i = 1; i <= L; ++i)
Z = (Z + g[O(n * d[i] + 1e-6)] * i % M * i) % M;
printf("%lld", Z);
return 0;
}
经测试,这份代码在 10s 内能跑到
Part II 积性函数求和
……Min_25 筛好像是用来做积性函数求和的来着?
即求
仿照上面的
Min_25
考虑
最小值因子
(最后一个等号成立的原因:考虑
至于
直接按照转移式开搜,边界条件
给出洛谷 P5325 的代码:
#include <cmath>
#include <cstdio>
#define M 1000000007
#define _2 500000004
#define _6 166666668
#define int long long
bool b[10000050];
double d[10000050];
int n, L, c, _, v[10000050], o1[10000050], o2[10000050], p[10000050], g[10000050][2], s[10000050][2];
int O(int x) { return x <= L ? o1[x] : o2[n / x]; }
int F(int k, int n)
{
if (p[k] > n)
return 0;
int q = (g[O(n)][1] + (M << 1) - g[O(n)][0] - s[k - 1][1] + s[k - 1][0]) % M;
for (int i = k; i <= c && p[i] * p[i] <= n; ++i) //枚举最小值因子 p[i]
for (int j = p[i], o = n; j * p[i] <= n; j *= p[i]) //枚举 j=p[i]^c,保证 p[i]^(c+1)<=n
{
int u = j % M, v = j * p[i] % M;
q = (q + u * (u + M - 1) % M * F(i + 1, o = o * d[p[i]] + 1e-6) + v * (v + M - 1)) % M;
//答案加上 f(p[i]^c)*F(i+1,n/p[i]^c)+f(p[i]^(c+1)),可以在上面的代码中一一对应
}
return q;
}
signed main()
{
scanf("%lld", &n);
L = sqrt(n), d[1] = 1;
for (int i = 2; i <= L; ++i)
{
if (!b[i])
p[++c] = i, s[c][0] = (s[c - 1][0] + i) % M, s[c][1] = (s[c - 1][1] + i * i) % M;
for (int j = 1; i * p[j] <= L; ++j)
{
b[i * p[j]] = 1;
if (!(i % p[j]))
break;
}
d[i] = 1.0 / i;
}
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
int u = n / l % M;
g[_][0] = (u * (u + 1) % M * _2 + M - 1) % M;
g[_][1] = (u * (u + 1) % M * (u << 1 | 1) % M * _6 + M - 1) % M;
}
for (int j = 1; j <= c; ++j)
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
{
int u = O(v[i] * d[p[j]] + 1e-6);
g[i][0] = (g[i][0] + M - p[j] * (g[u][0] + M - s[j - 1][0]) % M) % M;
g[i][1] = (g[i][1] + M - p[j] * p[j] % M * (g[u][1] + M - s[j - 1][1]) % M) % M;
}
//以上是 Part I,不再解释
printf("%lld", (F(1, n) + 1) % M); //别忘了 +1
return 0;
}
可见 Min_25 筛的码量并不大,具有一定的实战意义?
经测试,这份代码在 10s 内能跑到
Min_26
你应该可以猜到接下来会发生什么了(
考虑 根 号 分 治。
设
考虑对
把质数单独拿出来算,枚举有两个质因子的数的
接着,对
可以发现,
枚举这些数中
直接转移复杂度显然不对,继续根号分治。
仿照 Part I 的做法,对
对
然后这个还是后缀加,用树状数组维护。
最后,对
分析一下复杂度,首先算
接着,由
最后由
jijidawang 证明了前者有
给出洛谷 P5325 的代码:
#include <cmath>
#include <cstdio>
#define M 1000000007
#define _2 500000004
#define _6 166666668
#define int long long
bool b[10000050];
double d[10000050];
int n, L, c, _, n6, n3, n23, t6, t3, v[10000050], o1[10000050], o2[10000050], p[10000050], f[10000050], g[10000050][2], s[10000050][2], C[10000050];
int O(int x) { return x <= L ? o1[x] : o2[n / x]; }
void G(int x, int k)
{
for (; x; x &= x - 1)
C[x] = (C[x] + k) % M;
}
int W(int x)
{
int q = 0;
for (; x <= _; x += x & -x)
q = (q + C[x]) % M;
return q;
}
void D(int u, int fu, int l, int T) //与 Part I 不同的是,T=-1 时更新 f 而不是 g
{
G(O(n / (n / u)), ~T ? (M - fu) % M : fu);
for (int i = l; i <= c && u * p[i] < n23; ++i)
for (int j = p[i], fj = 1; u * j < n23; j *= p[i])
fj = fj = ~T ? fj * (s[i][T] + M - s[i - 1][T]) % M : j * (j - 1) % M, D(u * j, fu * fj % M, i + 1, T);
}
void U(int u, int T) //同上
{
if (~T)
G(O(n / (n / p[u])), (s[u][T] + M - s[u - 1][T]) % M);
for (int i = p[u], fi = 1; i < n23; i *= p[u])
fi = ~T ? fi * (s[u][T] + M - s[u - 1][T]) % M : i * (i - 1) % M, D(i, fi, u + 1, T);
}
signed main()
{
scanf("%lld", &n);
L = sqrt(n), n6 = pow(n, 1.0 / 6), n23 = pow(n, 2.0 / 3), n3 = sqrt(n23);
d[1] = t6 = t3 = 1;
for (int i = 2; i <= L + 1; ++i)
{
if (!b[i])
p[++c] = i, s[c][0] = (s[c - 1][0] + i) % M, s[c][1] = (s[c - 1][1] + i * i) % M;
for (int j = 1; i * p[j] <= L + 1; ++j)
{
b[i * p[j]] = 1;
if (!(i % p[j]))
break;
}
if (i == n6)
t6 += c;
if (i == n3)
t3 += c;
d[i] = 1.0 / i;
}
for (int l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l), v[++_] = n / l;
if (v[_] <= L)
o1[v[_]] = _;
else
o2[n / v[_]] = _;
int u = n / l % M;
g[_][0] = (u * (u + 1) % M * _2 + M - 1) % M;
g[_][1] = (u * (u + 1) % M * (u << 1 | 1) % M * _6 + M - 1) % M;
}
for (int T = 0; T < 2; ++T)
{
for (int j = 1; j < t6; ++j)
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i][T] = (g[i][T] + M - (s[j][T] + M - s[j - 1][T]) * (g[O(v[i] * d[p[j]] + 1e-6)][T] + M - s[j - 1][T]) % M) % M;
for (int i = _; v[i] < n23; --i)
C[i] = (g[i][T] + M - g[i + (i & -i)][T]) % M;
for (int j = t6; j < t3; ++j)
{
for (int i = 1; n23 <= v[i]; ++i)
{
int o = v[i] * d[p[j]] + 1e-6;
if (o < n23)
g[i][T] = (g[i][T] + M - (s[j][T] + M - s[j - 1][T]) * (W(O(o)) + M - s[j - 1][T]) % M) % M;
else
g[i][T] = (g[i][T] + M - (s[j][T] + M - s[j - 1][T]) * (g[O(o)][T] + M - s[j - 1][T]) % M) % M;
}
U(j, T);
}
for (int i = _; v[i] < n23; --i)
g[i][T] = W(i);
for (int j = t3; j <= c; ++j)
for (int i = 1; p[j] * p[j] <= v[i]; ++i)
g[i][T] = (g[i][T] + M - (s[j][T] + M - s[j - 1][T]) * (g[O(v[i] * d[p[j]] + 1e-6)][T] + M - s[j - 1][T]) % M) % M;
}
//以上是 Part I,不再解释
for (int i = 1; v[i] >= p[t3]; ++i)
{
f[i] = (g[i][1] + (M << 1) - g[i][0] - s[t3 - 1][1] + s[t3 - 1][0]) % M; //初始化 f[t3][n]=g[n]-s[t3-1]
for (int j = t3; j <= c && p[j] * p[j] <= v[i]; ++j)
{
int o = O(v[i] * d[p[j]] + 1e-6);
f[i] = (f[i] + p[j] * p[j] % M * (p[j] * p[j] % M - 1) + p[j] * (p[j] - 1) % M * (g[o][1] + (M << 1) - g[o][0] - s[j][1] + s[j][0])) % M;
//累加 f(p[j]^2)+f(p[j])*(g[n/p[j]]-s[j]),可以在上面的代码中一一对应
}
}
for (int i = _; v[i] < n23; --i)
C[i] = (f[i] + M - f[i + (i & -i)]) % M; //O(n) 建树状数组
for (int j = t3 - 1; j >= t6; --j)
{
for (int i = 1; n23 <= v[i]; ++i) //v[i]>=T 用转移式求 f[i]
for (int k = p[j], o = v[i]; k <= v[i]; k *= p[j]) //枚举 p[j]^c<=v[i]
{
o = o * d[p[j]] + 1e-6;
if (o < n23) //v[i]/p[j]^c<T 时,f[v[i]/p[j]^c] 在树状数组上
f[i] = (f[i] + k % M * (k % M - 1) % M * (W(O(o)) + 1)) % M;
else //v[i]/p[j]^c>=T 时,f[v[i]/p[j]^c] 是用转移式得到的,直接在 f 上查
f[i] = (f[i] + k % M * (k % M - 1) % M * (f[O(o)] + 1)) % M;
}
U(j, -1); //爆搜 lpf=p[j] 的数,更新 v[i]<T 的 f[i]
}
for (int i = _; v[i] < n23; --i)
f[i] = W(i); //从树状数组上取出 v[i]<T 的 f[i]
for (int j = t6 - 1; j >= 1; --j) //直接用转移式转移
for (int i = 1; v[i] >= p[j]; ++i)
for (int k = p[j], o = v[i]; k <= v[i]; k *= p[j])
f[i] = (f[i] + k % M * (k % M - 1) % M * (f[O(o = o * d[p[j]] + 1e-6)] + 1)) % M;
printf("%lld", (f[1] + 1) % M); //别忘了 +1
return 0;
}
经测试,这份代码在 10s 内能跑到
值得一提的是,这份代码在模板题跑不过 Min_25,不知道为什么。
zzt 求和法
大 的 药 来 了
如果你会 PN 筛,你可以跳过下面的缩进部分。
PN 筛:有积性函数
满足 ,已知 在 的所有块筛处的前缀和,求 。 做法:
称质因子次数最低二次的数为 Powerful Number(PN)。显然所有 PN 都能表示成
的形式。 考虑
以内 PN 的个数。有 个。 设积性函数
满足 ,则 , 又因为
,所以 ,则 只在 PN 处有值。则 爆搜
,统计答案即可。 注意跑 PN 筛的前提是已知
在 的所有块筛处的前缀和, 所以在 Min_25 筛板子题构造
并使用 PN 筛的复杂度不是 , 因为求
在 的所有块筛处的前缀和需要跑一遍 的杜教筛。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具