公式渲染测试。

前置知识

小碎骨

艾佛森括号
此处 是一个可真可假的命题。

引理1

证明

数论分块

内容独立了出来,详细内容见 数论分块 - Luckyblock

对于一类含有的求和式 ( 为常数),由于单调不增,故存在多个区间, 使得 成立。

对于任意一个,最大的满足上式的


积性函数

定义

,则 为积性函数。

性质

均为积性函数,则以下函数也为积性函数:
$$

常见积性函数

  • 单位函数
  • 幂函数 通常简记为
  • 常数函数
  • 因数个数
  • 除数函数 时为因数和函数,通常简记为 时为因数个数函数
  • 欧拉函数
  • 莫比乌斯函数

不是很懂上面写的什么玩意?
不用深究,有个印象继续往下看就好。


莫比乌斯函数

定义

为莫比乌斯函数,定义为
$$

解释

,其中为质因子,

1. 时,
2. 时 ,

  • 时,
    当某质因子出现次数大于时,
  • 时,
    当每个质因子只出现一次时,即中元素唯一。 ,此处为质因子的种类数。

性质

莫比乌斯函数是积性函数,且具有以下性质

证明,设

  • 根据莫比乌斯函数定义,则有:
  • 的某因子 ,有 ,则它由 个 本质不同的质因子组成。
    由于质因子总数为 ,满足上式的因子数为
  • 对于原求和式,转为枚举 的值。

    根据二项式定理,上式
    易知该式在 ,即 时为 ,否则为

反演常用结论

一个反演常用结论:
$$

证明 1:
,则右 左。

证明 2:
暴力展开:

线性筛求莫比乌斯函数

为积性函数,因此可以线性筛莫比乌斯函数。

int pnum, mu[kMaxn], p[kMaxn];
bool vis[kMaxn];

void Euler(int lim_) {
  vis[1] = true, mu[1] = 1ll;
  for (int i = 2; i <= lim_; ++ i) {
    if (! vis[i]) {
      p[++ pnum] = i;
      mu[i] = - 1;
    }
    for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
      vis[i * p[j]] = true;
      if (i % p[j] == 0) { //勿忘平方因子
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = - mu[i];
    }
  }
}

狄利克雷(Dirichlet)卷积

建议阅读 算法学习笔记(35): 狄利克雷卷积 By: Pecco

定义两个数论函数 的狄利克雷卷积为
$$

性质

1. 显然满***换律,结合律,分配律。


  • 2. 为狄利克雷卷积的单位元,有
    3. 若 为积性函数,则 为积性函数。

关于单位元

有:

证明

  • 对于,当且仅当 ,即 时为 ,否则为
  • 则当 时,
    时,

综上,,满足单位元的性质。
成立。

除数函数与幂函数

幂函数
除数函数

显然有:

时, 为因数个数函数,有:

欧拉函数与恒等函数

对于一式,当 时( 为质数),有:

的因子有 个,为 ,故

为积性函数,则对于任意正整数 ,有:

得证。

对于 2 式,在 1 式基础上两侧同时 即得。
左侧变为


莫比乌斯反演

反演是个啥?反演

公式

为两个数论函数。
如果有
$$

证明

方法一:运用卷积。

原问题为:已知 ,证明
易知如下转化:

方法二:对原式进行数论变换。

1. 用 替换
$$

2. 变换求和顺序。
$$

3. 依据 ,仅当 时,,否则为
此时,故上式等价于

举例

狄利克雷(Dirichlet)卷积 部分可以知道一些积性函数的关系。
尝试对它们进行反演:


关于一类求和式

一般套路

考虑构造出函数 ,满足下列形式:

则求和式变为:

考虑算术基本定理,发现若满足 ,则 成立。
考虑 在何时做出贡献,调整枚举顺序:

等价于 的倍数的个数,则上式等价于:

数论分块求解即可。

例 1

发现此题的 等价于 ,则上式等价于:

例 2

感悟

卷点什么东西,把 卷出来。 不一定是特殊意义的函数。


例题

[HAOI2011] Problem b

组询问,每次给定参数 ,求:
$1\le n,k,a,b,c,d\le 5\times 10^4a\le b,c\le d$。


根据容斥原理,则原式等价于 变成了上述一类求和式的形式,考虑化简

易知原式等价于
$$

代入反演常用结论 ,上式化为
$d\mid \gcd(i,j)d\mid id\mid jd\mid \gcd(i,j)$。

易知 的倍数有 个(由引理 1 可知),原式变为
$$

预处理 后,显然可以数论分块求解,复杂度


代码

//知识点:莫比乌斯反演
/*
//By:Luckyblock
*/
#include 
#include 
#include 
#define ll long long
const int MARX = 6e4 + 10;
//=============================================================
int N, a, b, c, d, k;
int cnt, Mobius[MARX], Prime[MARX], Sum[MARX];
bool vis[MARX];
//=============================================================
inline int read()
{
    int f = 1, w = 0; char ch = getchar();
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
    for(; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
    return f * w;
}
void GetMobius()
{
    Mobius[1] = 1;
    int MAX = MARX - 10;
    for(int i = 2; i <= MAX; i ++)
    {
      if(! vis[i]) Mobius[i] = - 1, Prime[++ cnt] = i;
      for(int j = 1; j <= cnt && i * Prime[j] <= MAX; j ++)
      {
        vis[i * Prime[j]] = true;
        if(i % Prime[j] == 0) break;
        Mobius[i * Prime[j]] = - Mobius[i];
      }
    }
    for(int i = 1; i <= MAX; i ++) Sum[i] = Sum[i - 1] + Mobius[i];
}
ll Calc(int x, int y)
{
    ll ans = 0ll; int max = std :: min(x, y);
    for(int l = 1, r; l <= max; l = r + 1)
      r = std :: min(x / (x / l), y / (y / l)),
      ans += (1ll * x / (1ll * l * k)) * (1ll * y / (1ll * l * k)) * (Sum[r] - Sum[l - 1]);
    return ans;
}
ll Solve()
{
    a = read(), b = read(), c = read(), d = read(), k = read();
    return Calc(b, d) - Calc(b, c - 1) - Calc(a - 1, d) + Calc(a - 1, c - 1);
}
//=============================================================
int main()
{ 
    N = read(); GetMobius();
    while(N --) printf("%lld\n", Solve());
    return 0;
}

[国家集训队]Crash的数字表格

给定 , 求:
$1\le n,m\le 10^7$。

易知原式等价于:

考虑枚举 ,设枚举量为
的充要条件是满足 ,则原式等价于:

先枚举 ,则原式等价于:

这个 很烦人,把 中的 提出来,变为枚举
消去 的限定条件,则原式等价于:

单独考虑后半部分,设
发现 的形式与 [HAOI2011] Problem b 中的式子类似,代入 套路一波:

前半部分 ,可以考虑筛出 后求前缀和。
后半部分是等差数列乘等差数列的形式,设 可以通过下式 计算:

则对于 ,有:

数论分块求解即可。

再看回原式,原式等价于:

又是一个可以数论分块求解的形式。
线性筛预处理后 数论分块套数论分块,复杂度 ,瓶颈是线性筛。


一些注意的点

处理时会出现求平方的运算,需要特别注意取模问题,ll 都会爆,被坑惨了。

在预处理前缀和的这个地方:

sum[i] = (sum[i - 1] + 1ll * i * i % kMod * (mu[i] + kMod)) % kMod; //仅令 mu + kMod

注意先对平方取模,在乘上 ,否则就会爆掉。
以及可以仅令

以及这个地方:

int g(int n_, int m_) {
  return (1ll * n_ * (n_ + 1ll) / 2ll % kMod) * (1ll * m_ * (m_ + 1ll) / 2ll % kMod) % kMod;
}

平方计算,注意随时取模。

代码

//莫比乌斯反演
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#include 
#define ll long long
const ll kMod = 20101009;
const int kMaxn = 1e7 + 10;
//=============================================================
int pnum, p[kMaxn];
ll mu[kMaxn], sum[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Getmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Getmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Euler(int lim_) {
  vis[1] = true, mu[1] = 1ll;
  for (int i = 2; i <= lim_; ++ i) {
    if (! vis[i]) {
      p[++ pnum] = i;
      mu[i] = - 1;
    }
    for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
      vis[i * p[j]] = true;
      if (i % p[j] == 0) { //勿忘平方因子
        mu[i * p[j]] = 0;
        break;
      }
      mu[i * p[j]] = - mu[i];
    }
  }
  sum[1] = 1ll;
  for (int i = 1; i <= lim_; ++ i) {
    sum[i] = (sum[i - 1] + 1ll * i * i % kMod * (mu[i] + kMod)) % kMod; //仅令 mu + kMod
  }
}
int g(int n_, int m_) {
  return (1ll * n_ * (n_ + 1ll) / 2ll % kMod) * (1ll * m_ * (m_ + 1ll) / 2ll % kMod) % kMod;
}
int f(int n_, int m_) {
  int lim = std :: min(n_, m_), ret = 0;
  for (int l = 1, r; l <= lim; l = r + 1) {
    r = std :: min(n_ / (n_ / l), m_ / (m_ / l));
    ret = (ret + 1ll * (sum[r] - sum[l - 1] + kMod) * g(n_ / l, m_ / l) % kMod) % kMod;
  }
  return ret;
}
int Sum(ll l, ll r) {
  return (1ll * (r - l + 1ll) * (l + r) / 2ll) % kMod;
}
//=============================================================
int main() { 
  int n = read(), m = read();
  int lim = std :: min(n, m), ans = 0;
  Euler(lim);
  for (int l = 1, r; l <= lim; l = r + 1) {
    r = std :: min(n / (n / l), m / (m / l));
    ans = (ans + 1ll * Sum(l, r) * f(n / l, m / l) % kMod) % kMod;
  }
  printf("%d", ans);
  return 0;
}
/*
7718820 8445880
*/

SP5971 LCMSUM - LCM Sum

次询问,每次询问给定 ,求:
$1<T\le 3\times 10^51\le n\le 10^6$。

法一:无脑暴力

先拆 ,原式等价于:

套路的枚举 ,调换枚举顺序,原式等价于:

中的 提出来,变为枚举 ,消去整除的条件,原式等价于:

代入 ,原式等价于:

值得注意的是 的上界为
调换枚举顺序,先枚举 ,原式等价于:

套路地消去整除的条件,把 中的 提出来,原式等价于:

对于最后的一个求和项,设 ,显然可以 求解,原式等价于:

考虑枚举 ,显然 无关,可以直接考虑枚举 ,原式等价于:

前半块是一个数论分块的形式,可以 求解。
考虑后半块,设 ,发现它是一个积性函数,可以线性筛筛出,具体地:

其中 的最小质因子。

此时已经可以线性筛 + 数论分块求解,复杂度 ,比较菜鸡,时限 500ms 过不了。
考虑筛出 后再用埃氏筛预处理 ,输出时乘上 ,复杂度变为


法二:

同样先拆 ,枚举 ,调换枚举顺序,原式等价于:

中的 提出来,变为枚举 ,消去整除的条件,原式等价于:

调整枚举对象,上式等价于:

考虑 的实际意义,表示 中与 互质的数的和。
时,与 互质的数总是成对存在,即若 成立,则 成立。
每对这样的数的和为 ,共有 对这样的数。
则原式等价于:
$$

可以直接预处理答案。
预处理时先线性筛出 ,再埃氏筛枚举 的倍数,令它们的答案加上 ,最后输出时乘上
复杂度


法二代码

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#define ll long long
const int kMaxn = 1e6;
//=============================================================
ll phi[kMaxn + 10], ans[kMaxn + 10];
int pnum, p[kMaxn + 10];
bool flag[kMaxn + 10];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void GetPrime() {
  phi[1] = 1, flag[1] = true; //注意初始化
  for (int i = 2; i <= kMaxn; ++ i) {
    if (! flag[i]) {
      p[++ pnum] = i;
      phi[i] = i - 1ll;
    }
    for (int j = 1; j <= pnum && i * p[j] <= kMaxn; ++ j) {
      flag[i * p[j]] = true;
      if (i % p[j]) {
        phi[i * p[j]] = phi[i] * phi[p[j]];
      } else {
        phi[i * p[j]] = phi[i] * p[j];
        break;
      }
    }
  }
  for (int i = 1; i <= kMaxn; ++ i) {
    for (int j = 1; i * j <= kMaxn; ++ j) {
      ans[i * j] += (i == 1 ? 1 : 1ll * phi[i] * i / 2);
    }
  }
}
//=============================================================
int main() { 
  GetPrime();
  int T = read();
  while (T --) {
    int n = read();
    printf("%lld\n", 1ll * ans[n] * n);
  }
  return 0; 
}

[SDOI2015]约数个数和

次询问,每次询问给定
定义 > 的约数个数,求:
$1<T,n\le 5\times 10^4$。

一个结论:

证明

先考虑 的情况,有:

对于等式左侧, 的约数个数为
对于等式右侧,在保证 成立的情况下,有贡献的数对 只能是下列三种形式:

  • 种取值方法。
  • 种取值方法。

则等式右侧贡献次数为 次,等于 的约数个数。
则当 时等式成立。

又不同质因子间相互独立,上述情况可拓展到一般的情况。


进行化简,代入 ,原式等价于:

调换枚举顺序,先枚举 ,原式等价于:

把各项中的 提出来,消去整除的条件,原式等价于:


代回原式,原式等价于:

调换枚举顺序,先枚举 ,原式等价于:

中的 提出来,变为枚举 ,消去整除的条件,原式等价于:

考虑预处理 ,则原式等价于:

线性筛预处理 ,数论分块求解即可,复杂度


代码

//知识点:莫比乌斯反演
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#include 
#define ll long long
const int kMaxn = 5e4 + 10;
//=============================================================
int pnum, p[kMaxn];
ll mu[kMaxn], num[kMaxn], d[kMaxn]; //num 为最小质因子的次数
ll summu[kMaxn], sumd[kMaxn];
bool vis[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Getmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Getmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Euler(int lim_) {
  vis[1] = true;
  mu[1] = d[1] = 1ll;
  for (int i = 2; i <= lim_; ++ i) {
    if (! vis[i]) {
      p[++ pnum] = i;
      mu[i] = - 1;
      num[i] = 1;
      d[i] = 2;
    }
    for (int j = 1; j <= pnum && i * p[j] <= lim_; ++ j) {
      vis[i * p[j]] = true;
      if (i % p[j] == 0) { //勿忘平方因子
        mu[i * p[j]] = 0;
        num[i * p[j]] = num[i] + 1;
        d[i * p[j]] = 1ll * d[i] / num[i * p[j]] * (num[i * p[j]] + 1ll);
        break;
      }
      mu[i * p[j]] = - mu[i];
      num[i * p[j]] = 1;
      d[i * p[j]] = 2ll * d[i]; //

    }
  }
  for (int i = 1; i <= lim_; ++ i) {
    summu[i] = summu[i - 1] + mu[i];
    sumd[i] = sumd[i - 1] + d[i];
  }
}

//=============================================================
int main() { 
  Euler(kMaxn - 10);
  int T = read();
  while (T --) {
    int n = read(), m = read(), lim = std :: min(n, m);
    ll ans = 0ll;
    for (int l = 1, r; l <= lim; l = r + 1) {
      r = std :: min(n / (n / l), m / (m / l));
      ans += 1ll * (summu[r] - summu[l - 1]) * sumd[n / l] * sumd[m / l]; //
    }
    printf("%lld\n", ans);
  }
  return 0;
}
/*
in
1
32 43
*/
/*
out
15420
*/

P3768 简单的数学题

给定参数 ,求:
$n\leq10^{10}5\times10^8\leq p\leq1.1\times10^9p\in \mathrm{primes}$。
时限 4s。

无脑套路暴力。

考虑先枚举 ,原式等价于:

提出 ,变为枚举 ,消去整除的条件,原式等价于:

代入 ,原式等价于:

值得注意的是 的上界为
调换枚举顺序,先枚举 ,原式等价于:

和上面一样,提出 ,套路地消去整除的条件,原式等价于:

发现后面两个求和是等差数列乘等差数列的形式。
可以通过下式 计算:

代入原式,原式等价于:

考虑枚举 ,显然
再考虑枚举 ,即可得到 ,原式等价于:

对于后面这一坨,用 反演,则原式等价于:

后半块可以数论分块,考虑前半块。
发现前半段即为 ,又是前缀和形式,考虑杜教筛。

有:

考虑找到一个函数 ,构造函数 使其便于求值,有:

看到同时存在 ,考虑把 消去。
,有:

,则有:

找到了合适的 ,套杜教筛的公式。

前一项是自然数的立方和,有 。证明详见:自然数前n项平方和、立方和公式及证明 - 百度文库
后一项直接等差数列求和 + 数论分块求解即可。


P5518 [MtOI2019]幽灵乐团 / 莫比乌斯反演基础练习题

因为非常喜欢这首曲子所以很想写下这题。
但是先咕咕咕咕着。


写在最后

参考资料:
Oi-Wiki-莫比乌斯反演
算法学习笔记(35): 狄利克雷卷积 By: Pecco
题解 SP5971 【LCMSUM - LCM Sum】 - BJpers2 的博客
题解 SP5971 【LCMSUM - LCM Sum】 - Venus 的博客
题解 P3327 【[SDOI2015]约数个数和】 - suncongbo 的博客

把 Oi-Wiki 上的内容进行了复制 整理扩充。
我是个没有感情的复读机(大雾)