一篇更好的文章 By skywalkert
前置技能
- 线性筛
- 容斥原理
函数的积性
如果对于任意和
满足
都有
,那么就称函数
具有积性,是一个积性函数。
狄利克雷卷积
和
两个函数的狄利克雷卷积为
其中,表示卷积符号。
关于狄利克雷卷积最重要的一点是如果和
都是积性的,那么
也是积性的(证明比较显然)。
呃……不如说狄利克雷卷积这种过于宽泛的东西就很难有什么特殊的性质吧。
一些将要用到的函数
呃……非常平凡的函数。
设,则
只是一个容斥系数,有时被称作莫比乌斯函数。而
的意义为与
互质的数的个数。
一些性质
证明:
设为
的不同质因子个数。
证明:
将的每个数
分类到桶
中。桶的编号都是
的约数,
号桶中恰好有
个数。统计每个桶中元素个数之和,一定会得到
。
的取值最多只有
个。
证明:
当时,
最多有
个。当
时,
,所以也只有
个。
是使
的最大的
。
证明:
考虑一下整除的意义,就会发现这是显然的。
证明:
设。则上式左右两边显然都等于
。
莫比乌斯反演
假设有函数和
,满足
则
证明:考虑在上式的右边被算了多少次。
这正好是刚刚的结论!事实上,我们也可以通过直接代入上面的结论来得到莫比乌斯反演:
Orz! 第一步说了一句“废话”,却在巧妙地交换求和顺序后把合并成了
,得到了莫比乌斯反演。无限仰慕给出了这个证明的VFK大爷!
莫比乌斯反演还有另一个形式:
则
证明过程是类似的。事实上,这种形式出现的次数更多一些。
应用:求
的个数
设为
为
显然有
对上式应用莫比乌斯反演,即得
这样,我们可以在的时间内求出答案。事实上,还可以更快一些:
因此,只需要考虑的情况。问题就转化为了
因为和
都只有
个可能的值,所以
也只有
个可能的值。又因为单调性,所以
的取值一定是
个连续段。对于每个连续段,可以预处理
的前缀和来一起计算。只要
求出每一段的上界和下界,就能在
的时间内解决一组询问。
设是
的取值中某个连续段的上界。根据整除的性质,容易发现
所在的连续段的上界是
,而满足
且和
不在一个连续段的最大的
,也就是
的上一个连续段的上界是
。分别找出
的连续段和
的连续段,就能得到
的连续段。
示例代码
BZOJ2301只要稍稍容斥一下就能转化为求的个数。
#include <bits/stdc++.h>
const int MAXN = 5e4, SIZ = MAXN + 10;
int pri[SIZ], pri_n, fac[SIZ], mu[SIZ];
void linear_sieve() {
mu[1] = 1;
for(int i = 2; i <= MAXN; i++) {
if(!fac[i]) pri[++pri_n] = fac[i] = i, mu[i] = -1;
for(int j = 1; j <= pri_n && pri[j] <= fac[i] && pri[j] * i <= MAXN; j++)
fac[i * pri[j]] = pri[j], mu[i * pri[j]] = pri[j] == fac[i] ? 0 : -mu[i];
}
for(int i = 2; i <= MAXN; i++) mu[i] += mu[i - 1];
}
inline int next(int a, int b) { return b / (b / a + 1); }
int solve(int n, int m) {
int ans = 0;
for(int i = n, j; i && (j = std::max(next(i, n), next(i, m)), true); i = j)
ans += (mu[i] - mu[j]) * (n / i) * (m / i);
return ans;
}
int main() {
linear_sieve();
int data; scanf("%d", &data);
while(data--) {
int a, b, c, d, k; scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
printf("%d\n", solve(b / k, d / k) - solve((a - 1) / k, d / k)
- solve(b / k, (c - 1) / k) + solve((a - 1) / k, (c - 1) / k));
}
}
应用:求gcd的k次方和
直接大力反演!
接下来,记为
,枚举
:
记。不难发现
就是
和
次幂函数的狄利克雷卷积。
和幂函数都是积性的,所以
也是积性的。并且对于质数的幂,容易求出
。因此我们可以用线性筛预处理
,然后问题的形式就变得和上一题一样了。我们可以
计算
和
的连续段,预处理
的前缀和,就能做到每组询问
。
示例代码
#include <bits/stdc++.h>
typedef long long ll;
const int MAXN = 5e6, SIZ = MAXN + 10, MOD = 1e9 + 7;
int N, M, K, T, pn, prm[SIZ], fct[SIZ], kthp[SIZ];
ll f[SIZ];
ll sqr(ll a) { return a * a % MOD; }
ll qpow(ll a, ll b) { return b ? sqr(qpow(a, b / 2)) * (b & 1 ? a : 1) % MOD : 1; }
int nxt(int a, int b) { return b / (b / a + 1); }
int main() {
scanf("%d%d", &T, &K);
f[1] = 1;
for(int i = 2; i <= MAXN; i++) {
if(!fct[i]) {
fct[i] = prm[++pn] = i;
kthp[i] = qpow(i, K); f[i] = (kthp[i] + MOD - 1) % MOD;
}
for(int j = 1; j <= pn && prm[j] <= fct[i] && prm[j] * i <= MAXN; j++) {
fct[prm[j] * i] = prm[j];
f[prm[j] * i] = (prm[j] == fct[i] ? f[i] * kthp[prm[j]] : f[i] * f[prm[j]]) % MOD;
}
}
for(int i = 2; i <= MAXN; i++) (f[i] += f[i - 1]) %= MOD;
for(int data = 1; data <= T; data++) {
scanf("%d%d", &N, &M);
ll ans = 0;
for(int i = N, j; i; i = j) {
j = std::max(nxt(i, N), nxt(i, M));
(ans += ll(N / i) * (M / i) % MOD * (f[i] - f[j])) %= MOD;
}
printf("%lld\n", ans);
}
}
杜教筛
设和
是任意函数,
,
,函数
是
和
的狄利克雷卷积,
。则
也就是说,
这说明如果对于给定的,我们找到了
,满足
的前缀和能够快速计算,
的前缀和也能快速计算,就能用这个公式来计算
:因为
的取值只有
种, 所以对于每种取值,可以用前缀和作差快速求出对应的
的和,然后递归计算
。 根据
,需要计算的一定是
的形式。可以用
unordered_map
记录计算过的的值,就能在
的时间内计算。为了分析这个复杂度,考虑
为了方便起见,假设具有
的形式。
因此,直接使用杜教筛求的复杂度是
。与此同时,我们还可以用线性筛预处理
的
来改进复杂度(显然至少预处理到
),改进后的复杂度为:
为了让复杂度尽量低,取,即
,即可做到
的复杂度。
应用:求欧拉函数和莫比乌斯函数的前缀和
BZOJ3944
最多有组询问。
记。由于
,也就是
把单独放到一边,也就是
这就是一个非常标准的杜教筛的形式了。再考虑的和:记
。由于
,也就是
把单独放到一边,也就是
为了记录下计算过的和
,除了使用unordered_map,还可以把
存在某个数组的第
个位置上。除去线性筛预处理的部分,
只有
种可能的取值。
示例代码
#include <bits/stdc++.h>
const int SIEVE = 5e6, SIEVE_SIZ = SIEVE + 10, SQRT_SIZ = 5e5;
typedef long long ll;
int N, pri[SIEVE_SIZ], fac[SIEVE_SIZ], pri_n;
ll phi[SIEVE_SIZ], mu[SIEVE_SIZ], ans_phi[SQRT_SIZ], ans_mu[SQRT_SIZ];
bool vis[SQRT_SIZ];
void init() {
phi[1] = mu[1] = 1;
for(int i = 2; i <= SIEVE; i++) {
if(!fac[i]) pri[++pri_n] = fac[i] = i, phi[i] = i - 1, mu[i] = -1;
for(int j = 1; j <= pri_n && pri[j] <= fac[i] && pri[j] * i <= SIEVE; j++) {
fac[pri[j] * i] = pri[j];
phi[pri[j] * i] = pri[j] == fac[i] ? phi[i] * pri[j] : phi[i] * (pri[j] - 1);
mu[pri[j] * i] = pri[j] == fac[i] ? 0 : -mu[i];
}
}
for(int i = 2; i <= SIEVE; i++) phi[i] += phi[i - 1], mu[i] += mu[i - 1];
}
inline int next(int a, int b) { return b / (b / (a + 1)); }
void solve(int x, ll &Phi, ll &Mu) {
ll y = N / x;
if(y <= SIEVE) {
Phi = phi[y], Mu = mu[y]; return;
}
if(vis[x]) {
Phi = ans_phi[x], Mu = ans_mu[x]; return;
}
Phi = y * (y + 1) / 2, Mu = 1;
for(int i = 1, j; i != y; i = j) {
j = next(i, y);
ll a, b; solve(x * j, a, b);
Phi -= (j - i) * a; Mu -= (j - i) * b;
}
ans_phi[x] = Phi, ans_mu[x] = Mu, vis[x] = true;
}
int main() {
init();
int T; scanf("%d", &T);
while(T--) {
scanf("%d", &N);
memset(vis, false, sizeof(vis));
ll Phi, Mu; solve(1, Phi, Mu);
printf("%lld %lld\n", Phi, Mu);
}
}
应用:求更复杂的函数的前缀和
求
记。可以发现
。也就是说
把单独提到一边:
只要最后把除掉就行了。这个例子主要是为了说明卷积的函数不一定是
。
推荐题目
请移步一篇更好的文章 By skywalkert,我是个大蒟蒻没刷过什么题Orz
Orz