第一次写莫比乌斯反演,感觉这个东西真的神奇,之前就因为使用莫比乌斯函数做容斥原理就久闻莫比乌斯反演大名,今日终于大概搞懂了莫比乌斯反演,其实说白了就是两个函数满足如下条件时
有如下反演存在
这个式子就叫做莫比乌斯反演,其中u为莫比乌斯函数。
这只是一种最常用的表示的方法,还有一种表示方法在信息学竞赛中更为常用,其形式如下
这道题用到的即为这种表示方法,让我们来看看莫比乌斯反演是如何优化这道题的。
首先我们看到一个询问是两段区间,那么我们就用容斥原理将这个询问拆成四个来做,首先我们看得出求出两个数的gcd为k并不好搞,那么我们就将n,m同除以一个k,这样找区间内互质的数的个数即可,我们设一个函数f(i)表示两个数gcd为i的答案的个数,这个东西显然不太好搞,但是如果有一个函数F(i)表示i|gcd(x,y)的数对的总数的话就比较无脑了,这个值我们可以很容易的推出来应该等于(n/i)*(m/i)(下取整),那么根据刚才的第二个反演公式则有如下表达式
由于我们要找的是互质的数对的个数,所以这里的i取1,将一代入式子以后我们发现变成了一个求和的问题,但是每次都暴力求肯定是不行的,所以我们搞一些玄学的优化,我们发现(n/d)最多只有2*根号n个取值,m也同理,那么(n/d)*(m/d)也就只有根号n加根号m个取值(注意不是乘),那么我们枚举这个取值再乘上这个取值的个数即可,这个个数就是对u进行前缀和的处理,得到边界的方法也极其的简单,这样的话查询时间复杂度就被我们优化成了根号n的级别,这道题就可以水过了。
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<cstring>
#include<string>
#include<iostream>
#include<iomanip>
#include<algorithm>
using namespace std;
int u[100100];
int zhi[100100];
bool pd[100100];
int top=0;
void shai()
{
u[1]=1;
for(int i=2;i<=100000;i++)
{
if(!pd[i]) zhi[++top]=i,u[i]=-1;
for(int j=1;j<=top && i*zhi[j]<=100000;j++)
{
pd[i*zhi[j]]=true;
if(i%zhi[j]==0) break;
u[i*zhi[j]]=-u[i];
}
}
for(int i=1;i<=100000;i++) u[i]+=u[i-1];
}
long long jisuan(int n,int m,int k)
{
n/=k;
m/=k;
int last;
long long re=0;
for(int i=1;i<=n && i<=m;i=last+1)
{
last=min(n/(n/i),m/(m/i));
re+=(long long)(n/i)*(m/i)*(u[last]-u[i-1]);
}
return re;
}
int main()
{
int n;
scanf("%d",&n);
shai();
for(int i=1;i<=n;i++)
{
int a,b,c,d,k;
scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
printf("%lld\n",jisuan(b,d,k)+jisuan(a-1,c-1,k)-jisuan(a-1,d,k)-jisuan(b,c-1,k));
}
}