简介: 对于一些函数(f(n)),如果很难直接求出他的值,而容易其倍数或约数和(g(n)),可以通过莫比乌斯反演简化问题
前置知识:
- 数论分块
其实在之前的博客里,做余数求和的时候总结过,但是也忘得差不多啦
数论分块的过程:考虑求含有(leftlfloor frac{n}{i} ight floor)的求和式子
对于任意一个(i)((ileq n)),我们需要找到一个最大的(j)((ileq j leq n)),满足(leftlfloor dfrac{n}{i} ight floor = leftlfloor dfrac{n}{j} ight floor),此时(j=leftlfloor frac{n}{leftlfloor frac{n}{i} ight floor} ight floor)
这样我们可以把一些值相同的数分成一大块一起计算,是不是方便很多?
证明:
(k = leftlfloor frac{n}{i} ight floor),考虑证明当(leftlfloor frac{n}{j} ight floor = k)时,(j)的最大值为(leftlfloor frac{n}{k} ight floor)
(leftlfloor frac{n}{j} ight floor = 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} = leftlfloor frac{n}{k} ight floor),这样我们每次以([i,j])分块求和即可
- 莫比乌斯函数
(mu)为莫比乌斯函数,定义为:(mu (n) = egin{cases}1&n=1\0&n含有平方因子\{(-1)}^k&k为n的本质不同的质因子个数end{cases})
性质:
-
(sumlimits_{dmid n} mu (d) = egin{cases} 1&n = 1\0&n eq 1end{cases})
-
([gcd(i,j)==1] Longleftrightarrow sumlimits_{dmid 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) = sumlimits_{dmid n}g(d)),那么有(g(n) = sumlimits_{dmid n} mu (d)f(frac{n}{d}))
如果有(f(n) = sumlimits_{dmid n}g(d)),那么有(g(n) = sumlimits_{dmid n} mu (frac{n}{d})f(d))
例题:
problem:
求符合(gcd(x,y) = k)的((x,y))的个数
solution:
(sumlimits_{i=1}^nsumlimits_{j=1}^m [gcd(i,j) = k] Longleftrightarrow sumlimits_{i=1}^{leftlfloorfrac{n}{k} ight floor}sumlimits_{j=1}^{leftlfloorfrac{m}{k} ight floor}[gcd(i,j) = 1] Longleftrightarrow sumlimits_{i=1}^{leftlfloorfrac{n}{k} ight floor}sumlimits_{j=1}^{leftlfloorfrac{m}{k} ight floor} sumlimits_{dmid gcd(i,j)} mu (d))
交换求和顺序,枚举(dmid gcd(i,j))即可
(sumlimits_{d=1} mu (d)sumlimits_{i=1}^{leftlfloorfrac{n}{k} ight floor} [dmid i]sumlimits_{j=1}^{leftlfloorfrac{m}{k} ight floor} [dmid j]Longleftrightarrow sumlimits_{d=1} mu (d)leftlfloor frac{n}{k} ight floorleftlfloorfrac{m}{k} ight floor)
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
", 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;
}