题目链接:bzoj3994
题目大意:
设d(x)为x的约数个数,给定N、M,求
∑ni=1∑mj=1d(i∗j)
题解:
莫比乌斯反演
−−−−−−
知识储备
公式:
d(i∗j)=∑x|i∑y|j[gcd(x,y)=1]
证明转自http://blog.csdn.net/clover_hxy/article/details/51282493 修改了一下变量名
证明如下:我们知道了这个自变量的乘积拆分形式,那么如果我们枚举 i 的所有约数
x ,枚举 j 的所有约数y ,那么 x∗y 一定是 i∗j 的一个约数,于是所有的 x∗y 就包括了 ij 的所有约数,那么我们只要知道有多少不同的 x∗y 就可以了。为了知道不同的 x∗y ,我们可以考虑如何去掉重复的 x∗y 。这里我们考虑把 x 和y 分为互质和不互质两种情况,设 gcd(x,y)=k ,于是 x=p∗k , y=q∗k 。那么 x∗y 就可以重新组合成两个互质数的积 p∗k∗k 和 q ,而这两个数也分别是i 和 j 的约数,即合法的x 和 y 。这一点说明了一个问题:如果枚举到的x 和 y 不互质,那么x∗y 这个数一定可以由另外一对互质的 x′ 和 y′ 表示,也就是这一对 x 和y 是重复的;而当 x 和y 互质的时候, p∗k∗k 和 q 显然就是x 和 y ,所以它是唯一的。于是我们总结一下上面的结论:所有x 和 y 的积包括了i∗j 的所有约数,当 x 和y 不互质时, x∗y 可以由另一对互质的 x′ 和 y′ 表示。所以我们得到所有互质的 x 和y 的乘积就不重复地包含了 ij 的所有约数。
−−−−−−
所以要求的式子即为:
化简
反演一下
继续套路化简,注意这里x,y的意义改变了
然后就可以写成:
可以发现后面两个式子能等价于 ∑ni=1⌊ni⌋ 即前n个数约数个数的和,即 ∑ni=1d(i) 。这个是可以线性筛出来的(后来才知道orz)。于是我们用 O(n) 的时间预处理 μ(i) 和 d(i) 及其前缀和。
然后将这后面两个值相等的一起算,即来进行分块,就能在 O(n√) 的时间内出解了。
嗯,这道题我经历了从11s到2s的蜕变。还是学了点东西的。
一开始不知道
d(i)
可以直接线性筛于是打了个分块计算,而且懒直接全部改成LL跑成了11s,然后把不必要的改回int后直接跳到了6s。但是我翻记录,发现大部分都是两秒多。看了别人的才知道
d(i)
是可以线性筛的。
//最后推荐一篇总结http://blog.csdn.net/loi_dqs/article/details/50539123
//屏蔽里的是原来求
d(i)
时的分块打法
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
#define N 50100
bool ispri[N];
int cnt,pri[N/4];LL mu[N],d[N],ct[N];
//ct[i]表示i的最小质因数的指数
//d[i]表示i的约数个数 [-后变其前缀和
int mymin(int x,int y){return (x<y)?x:y;}
void mobius(int lim)
{
cnt=0;mu[1]=1;d[1]=1;
for (int i=2;i<=lim;i++)
{
if (!ispri[i]) {pri[++cnt]=i;mu[i]=-1;d[i]=2;ct[i]=1;}
for (int j=1;j<=cnt && i*pri[j]<=lim;j++)
{
int k=i*pri[j];
ispri[k]=true;
if (i%pri[j]==0)
{
mu[k]=0;ct[k]=ct[i]+1;
d[k]=d[i]/(ct[i]+1)*(ct[k]+1);
break;
}
mu[k]=-mu[i];
ct[k]=1;d[k]=d[i]*2;
}
}
for (int i=2;i<=lim;i++) mu[i]+=mu[i-1],d[i]+=d[i-1];
}
// void pre(int lim)
// {
// int n,i,x,r;
// for (n=1;n<=lim;n++)
// {
// d[n]=0;
// for (i=1;i<=n;i=r+1)
// {
// x=n/i;r=mymin(n,n/x);
// d[n]+=(LL)(r-i+1)*x;
// }
// }
// }
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
int T,n,m,lim,x,y,lx,ly,i,r;
scanf("%d",&T);
mobius(50000);//pre(50000);
while (T--)
{
scanf("%d%d",&n,&m);
lim=mymin(n,m);LL ans=0;
for (i=1;i<=lim;i=r+1)
{
x=n/i;y=m/i;
lx=n/x;ly=m/y;
r=mymin(lx,ly);
ans+=d[x]*d[y]*(mu[r]-mu[i-1]);
}
printf("%lld\n",ans);
}
return 0;
}