链接
https://www.luogu.org/problem/show?pid=3455
莫比乌斯反演
所谓反演,就是类似于逆运算之类的一种东西。
像加的逆运算就是减,比如你知道
a+b=c
,又知道了
b
和
对于一种递推式:
那么
f(n)=∑d|nμ(d)F(nd)
还有一种形式:
F(n)=∑n|df(d)
推出
f(n)=∑n|dμ(dn)F(d)
其中 μ(x)被定义为
μ(x)=⎧⎩⎨ 1(−1)k 0x=1x=p1p2p3...pkelse
题解
这是一个经典的莫比乌斯反演问题。
设
f(i)
表示
x∈[1,N],y∈[1,M]
且
gcd(x,y)=i
的数对个数。
F(i)
表示范围内
gcd(x,y)
是
i
的倍数的数对个数。一个数对要满足它们的
F(x)=∑x|dNf(d)
反演
f(x)=∑x|dNμ(dx)F(d)=∑x|dNμ(dx)⌊Nd⌋⌊Md⌋
用 d 表示是
f(x)=∑d=1N/xμ(d)⌊Ndx⌋⌊Mdx⌋
随便手动模拟几组数据,会发现由于整除,会有一些神奇的事情发生:
5/1=55/2=25/3=15/4=15/5=1
可以证明, ⌊N1⌋ 到 ⌊NN⌋ 不同数字的数量级在 N−−√
所以可以预处理 μ() 函数的前缀和,根据结合律跳着计算答案即可。计算的方法在网上很普及了,详见代码。
代码
//莫比乌斯反演
#include <cstdio>
#include <algorithm>
#define maxn 50000
using namespace std;
int mu[maxn+10], mark[maxn+10], prime[maxn+10], s[maxn+10];
void init()
{
int i, j;
mu[1]=1;
for(i=2;i<=maxn;i++)
{
if(!mark[i])prime[++prime[0]]=i,mu[i]=-1;
for(j=1;j<=prime[0] and i*prime[j]<=maxn;j++)
{
mark[i*prime[j]]=1;
if(i%prime[j]==0){mu[i*prime[j]]=0;break;}
mu[i*prime[j]]=-mu[i];
}
}
for(i=1;i<=maxn;i++)s[i]=s[i-1]+mu[i];
}
int calc(int n, int m)
{
int i, last, ans=0;
if(n>m)swap(n,m);
for(i=1;i<=n;i=last+1)
{
last=min(n/(n/i),m/(m/i));
ans+=(n/i)*(m/i)*(s[last]-s[i-1]);
}
return ans;
}
int main()
{
int T, a, b, d;
init();
for(scanf("%d",&T);T;T--)
{
scanf("%d%d%d",&a,&b,&d);
printf("%d\n",calc(a/d,b/d));
}
return 0;
}