题目描述
设d(x)为x的约数个数,给定N、M,求 i=1∑Nj=1∑Md(ij)
N,M,T<=50000
题目分析
首先很不显然的有这样一个结论:
d(ij)=x∣i∑y∣j∑[(x,y)==1]
证明如下
将一个数唯一质因数分解为p1k1p2k2p3k3...pnkn,其约数个数为(k1+1)(k2+1)(k3+1)...(kn+1)
考虑一个质数p对d(ij)的影响,假设i分解质因数后有k个p,j分解质因数后有q个p,则产生的贡献即为k+q+1,接下来是关键的一步(反正我想不到XD)
k+q+1=x=0∑ky=0∑q[(px,py)==1]
(此句可自行忽略:只有当xy=0时才会有值,这就相当于一个(k+1)∗(q+1)的矩形,只取了左下角的L字型的一块)
根据乘法原理,有
d(ij)=x1=0∑k1y1=0∑q1[(p1x1,p1y1)==1]∗x2=0∑k2y2=0∑q2[(p2x2,p2y2)==1]⋅⋅⋅∗xn=0∑knyn=0∑qn[(pnxn,pnyn)==1]
因为必须满足所有(pnxn,pnyn)==1才会对答案造成贡献,则可以变形为(i=1∏npnxn,i=1∏npnyn)==1
所以d(ij)=x∣i∑y∣j∑[(x,y)==1]
(以上证明摘自:传送门)
证毕
现在就有了Ans=i=1∑Nj=1∑Mx∣i∑y∣j∑[(x,y)==1]
根据数论中切换枚举次序的套路以及莫比乌斯反演对布尔条件框的替换,我们得到
Ans=x=1∑Ny=1∑M⌊xN⌋⌊yM⌋[(x,y)==1]=x=1∑Ny=1∑M⌊xN⌋⌊yM⌋k∣(x,y)∑μ(k)=k=1∑min(N,M)μ(k)k∣x∑⌊xN⌋k∣y∑⌊yM⌋=k=1∑min(N,M)μ(k)x=1∑⌊kN⌋⌊kxN⌋y=1∑⌊kM⌋⌊kyM⌋
于是这里使用分块优化,预处理μ的前缀和
最后考虑后面的两个Sigma怎么处理
可以观察发现它们都是∑i=1n⌊in⌋的形式,此处可以用分块优化Θ(nn)预处理
其实还可以Θ(n)预处理,可以发现∑i=1n⌊in⌋=∑i=1n∑i∣dn1
显然是约数个数和d(n)的前缀和函数,于是线性筛出μ(n)与d(n),求出前缀和即可
每次询问Θ(n), 预处理Θ(n),总时间复杂度为Θ(nn)
TIPS
线性筛约数个数时存一下最小的质数p1的次方+1,即存下(k1+1)就可以方便的线性筛了
线性筛约数和同理,存一下最小的质数p1所对应的首项为1,公比为p1,项数为k1+1的等比数列,即存下∑i=1k1p1i就可以愉快的线性筛了
AC code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 100001;
namespace Mobius
{
#define LL long long
int Prime[MAXN], Cnt, mu[MAXN], d[MAXN], Minseq[MAXN];
bool IsnotPrime[MAXN];
LL sum_MU[MAXN], sum_D[MAXN];
void init()
{
mu[1] = d[1] = Minseq[1] = 1;
for(int i = 2; i < MAXN; ++i)
{
if(!IsnotPrime[i])
Prime[++Cnt] = i, mu[i] = -1, d[i] = Minseq[i] = 2;
for(int j = 1; j <= Cnt && i * Prime[j] < MAXN; ++j)
{
IsnotPrime[i * Prime[j]] = 1;
if(i % Prime[j] == 0)
{
Minseq[i * Prime[j]] = Minseq[i]+1;
mu[i * Prime[j]] = 0;
d[i * Prime[j]] = d[i] / Minseq[i] * Minseq[i * Prime[j]];
break;
}
Minseq[i * Prime[j]] = 2;
mu[i * Prime[j]] = -mu[i];
d[i * Prime[j]] = d[i]<<1;
}
}
for(int i = 1; i < MAXN; ++i)
sum_MU[i] = sum_MU[i-1] + mu[i],
sum_D[i] = sum_D[i-1] + d[i];
}
LL calc(int N, int M)
{
if(N > M) swap(N, M);
LL ret = 0;
for(int i = 1, j; i <= N; i=j+1)
{
j = min(N/(N/i), M/(M/i));
ret += (sum_MU[j]-sum_MU[i-1]) * sum_D[N/i] * sum_D[M/i];
}
return ret;
}
}
using namespace Mobius;
int main ()
{
init();
int T, n, m;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &n, &m);
printf("%lld
", calc(n, m));
}
}