莫比乌斯反演--「HAOI 2011」Problem b
简介: 对于一些函数\(f(n)\),如果很难直接求出他的值,而容易其倍数或约数和\(g(n)\),可以通过莫比乌斯反演简化问题
前置知识:
- 数论分块
其实在之前的博客里,做余数求和的时候总结过,但是也忘得差不多啦
数论分块的过程:考虑求含有\(\left\lfloor \frac{n}{i} \right\rfloor\)的求和式子
对于任意一个\(i\)\((i\leq n)\),我们需要找到一个最大的\(j\)\((i\leq j \leq n)\),满足\(\left\lfloor \dfrac{n}{i}\right\rfloor = \left\lfloor \dfrac{n}{j}\right\rfloor\),此时\(j=\left\lfloor \frac{n}{\left\lfloor \frac{n}{i}\right\rfloor}\right\rfloor\)
这样我们可以把一些值相同的数分成一大块一起计算,是不是方便很多?
证明:
\(k = \left\lfloor \frac{n}{i}\right\rfloor\),考虑证明当\(\left\lfloor \frac{n}{j}\right\rfloor = k\)时,\(j\)的最大值为\(\left\lfloor \frac{n}{k}\right\rfloor\)
\(\left\lfloor \frac{n}{j}\right\rfloor = k \Longleftrightarrow k \leq \frac{n}{j}<k+1 \Longleftrightarrow \frac{1}{k+1} < \frac{j}{n}\leq \frac{1}{k} \Longleftrightarrow \frac{n}{k+1} < j \leq \frac{n}{k+1}\)
又因为\(j\)是整数,所以\(j_{max} = \left\lfloor \frac{n}{k} \right\rfloor\),这样我们每次以\([i,j]\)分块求和即可
- 莫比乌斯函数
\(\mu\)为莫比乌斯函数,定义为:\(\mu (n) = \begin{cases}1&n=1\\0&n含有平方因子\\{(-1)}^k&k为n的本质不同的质因子个数\end{cases}\)
性质:
-
\(\sum\limits_{d\mid n} \mu (d) = \begin{cases} 1&n = 1\\0&n \neq 1\end{cases}\)
-
\([gcd(i,j)==1] \Longleftrightarrow \sum\limits_{d\mid gcd(i,j)} \mu (d)\)
code:
void init(){
mo[1] = 1;
for (int i = 2;i <= 1e6;i++){
if (!death[i]) primelist[++tot] = i,mo[i] = -1;
for (int j = 1;j <= tot&&i*primelist[j] <= 1e6;j++){
death[i*primelist[j]] = 1;
if (i%primelist[j] == 0){
mo[i*primelist[j]] = 0;
break;
}
mo[i*primelist[j]] = -mo[i];
}
}
for (int i = 1;i <= 1e6;i++) mo[i] += mo[i-1];
}
- 莫比乌斯反演
公式:
定义\(f(n)\),\(g(n)\)为两个数论函数
如果有\(f(n) = \sum\limits_{d\mid n}g(d)\),那么有\(g(n) = \sum\limits_{d\mid n} \mu (d)f(\frac{n}{d})\)
如果有\(f(n) = \sum\limits_{d\mid n}g(d)\),那么有\(g(n) = \sum\limits_{d\mid n} \mu (\frac{n}{d})f(d)\)
例题:
problem:
求符合\(gcd(x,y) = k\)的\((x,y)\)的个数
solution:
\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m [gcd(i,j) = k] \Longleftrightarrow \sum\limits_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\frac{m}{k}\right\rfloor}[gcd(i,j) = 1] \Longleftrightarrow \sum\limits_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\frac{m}{k}\right\rfloor} \sum\limits_{d\mid gcd(i,j)} \mu (d)\)
交换求和顺序,枚举\(d\mid gcd(i,j)\)即可
\(\sum\limits_{d=1} \mu (d)\sum\limits_{i=1}^{\left\lfloor\frac{n}{k}\right\rfloor} [d\mid i]\sum\limits_{j=1}^{\left\lfloor\frac{m}{k}\right\rfloor} [d\mid j]\Longleftrightarrow \sum\limits_{d=1} \mu (d)\left\lfloor \frac{n}{k}\right\rfloor\left\lfloor\frac{m}{k}\right\rfloor\)
code:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
const int maxn = 5e4+10;
int n;
int a,b,c,d,k;
int death[maxn],primelist[maxn],tot,mu[maxn];
void init(){
mu[1] = 1;
for (int i = 2;i <= 50000;i++){
if (!death[i]) primelist[++tot] = i,mu[i] = -1;
for (int j = 1;j <= tot&&i*primelist[j] <= 50000;j++){
death[i*primelist[j]] = 1;
if (i%primelist[j] == 0){
mu[i*primelist[j]] = 0;
break;
}
mu[i*primelist[j]] = -mu[i];
}
}
for (int i = 1;i <= 50000;i++) mu[i] += mu[i-1];
}
int solve(int n,int m){
int res = 0;
for (int l = 1,r;l <= min(n,m);l = r+1){
r = min(n/(n/l),m/(m/l));
res += (mu[r]-mu[l-1])*(n/l)*(m/l);
}
return res;
}
int main(){
n = read();init();
for (int i = 1;i <= n;i++){
a = read(),b = read(),c = read(),d = read(),k = read();
printf("%d\n", solve(b/k,d/k)-solve(b/k,(c-1)/k)-solve((a-1)/k,d/k)+solve((a-1)/k,(c-1)/k));
}
return 0;
}