这个题一眼上去应该不是很难,但除了求约数个数有个分解质因数的技巧之外就不会什么了;;
本来想着预处理50000以内的约数个数和之后求个前缀和直接乘、、、可见多么蠢、、搞了2h后弃疗了
结果题解是 要算gcd (。_。)
还有莫比乌斯反演、、、 …(⊙_⊙;)…
好吧,先借机学一下莫比乌斯反演 、、
其实这道题几乎就是为莫比乌斯而生的(板子题)
似乎是函数的反函数、、
所以叫反演、、 (*゜ー゜*)
而这个μ函数就是传说中的的莫比乌斯函数;
要注意是F(n/d)
那这个要怎么用?
那此题来说,首先要证明这个式子:
为什么会和gcd有关?
若i和j互质,那么i*j就是对于d(mn)唯一的约数
如果不互质,那么i和j还能再拆出几个数,作为约数
如 i=4 j=6,n=8,j=12;;
i=2*2 j=2*3
那么 i*j=2*2*2*3
可以写出互质的8 * 3
在循环到i=8,j=3时会被算进去、
而这些拆出的数一定可以对答案贡献1
所以用了bool;;
然后原式相当于求1~n、1~m的和:
根据 sigma交换律 和 可以处理处的某个积性函数μ可以得出莫比乌斯反演,:
(就是套公式, 【gcd(a,b)==1】= Σ(d|i,d|j)μ(d) )
注意μ是根据d能否整除i、d能否整除j 对d的一个函数,返回数值
有人可能要问gcd的判断和 i、j、d的整除关系得到的函数有什么关系?
注意多了一个Σ:也就是说它是先用前面四坨Σ枚举了1~n、 1~m所有可能的约数、、而这最后一个Σ则是枚举约数的约数、、(当然,默认N<M)
若d既整除了i,又整除了j,则i和j不互质,它有公因数d、、、
然后就不会了
我们注意到会有很多重复,重复的个数为 下取整(N/i) 所以可以用乘法优化加法,干掉两个Σ、、
这是反演的一步通用优化
再从d(约数)的角度交换一下位置:
对于每个d都要乘每种可能的i、j搭配,但对于n、m只会出现n/d、m/d次,每次搭配又会产生n/(i*d)、 m/(j*d)个重复
所以都可以乘起来再加
把最右边两项的d写到分子:
我们可以发现有两个不相关的项,可以独立处理:
而且这两个函数计算方式一毛一样,只是定义域值域不一样所以:
就可以化为:
分别处理f函数,再用线筛求出μ函数,就可以做了;;;
码:
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int mu[50001];
ll f[50001],ans;
int i,j,n,tot,su[50001],k,t,m;
bool vis[50001];
void eular()
{
mu[1]=1;
for(int i=2;i<=50000;i++)
{
if(!vis[i])
{
su[++tot]=i;
mu[i]=-1;//质数统一为 -1
}
for(j=1;i*su[j]<50000&&j<=tot;j++)
{
vis[i*su[j]]=1;
if(i%su[j])
{
mu[i*su[j]]=-mu[i];//合数如果没有>2的次方的质数就看元素个数(取反,即为(-1)^n)
}else
{
mu[i*su[j]]=0;//合数如果是p^n形式,mu就是0
break;
}
}
}
for (i=2; i<=50000; i++) mu[i]+=mu[i-1]; //处理前缀和(因为题目有Σ)
for (i=1; i<=50000; i++) //计算f
for (j=1; j<=i; j=k+1){
k=i/(i/j); //可能有点mengbi,k存的是最大的i/j,同 余数之和sum一题
f[i]+=(ll)(k-j+1)*(i/j); //用乘法加速连加
}
}
int main()
{ scanf("%d",&t); eular();
while(t--)
{
scanf("%d%d",&n,&m);
ans=0;
for (i=1; i<=m && i<=n; i=j+1){//枚举p,计算贡献
j=min(m/(m/i),n/(n/i)); //同余数之和sum的计算方式
ans+=f[m/i]*f[n/i]*(mu[j]-mu[i-1]);//用公式+前缀和统计答案
}
if(t)printf("%lld\n",ans);
else printf("%lld",ans);
}
}
莫比乌斯反演就是一个渠道,可以通过这个渠道对看似没有办法化简的式子转化为若干子函数,这样复杂度就会降低、
但式子的转化极其灵活,必须确保每一步正确性,
可以说,能用莫比乌斯反演的题都需要极强的数学敏感度和缜密的推理
当然,暴力性价比高、
附一张反演函数计算法则: