BZOJ 3994 约数个数和

莫比乌斯反演?不知道有没有用到。

原式=∑(x=1...n)μ(x)∑(i=1..[n/x])d(i)∑(j=1..[m/x])d(j)。

对miu,d分别前缀和,再对d*d进行分块。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define maxn 50010
using namespace std;
long long miu[maxn],prime[maxn],e[maxn],k[maxn],pre[maxn],cnt=0,premiu[maxn];
long long tk,n,m;
bool vis[maxn];
void get_prime()
{
memset(vis,false,sizeof(vis));
e[1]=1;k[1]=1;miu[1]=1;
for (long long i=2;i<=maxn-5;i++)
{
if (vis[i]==false)
{
prime[++cnt]=i;
e[i]=1;k[i]=2;
miu[i]=-1;
}
for (long long j=1;j<=cnt && i*prime[j]<=maxn-5;j++)
{
vis[i*prime[j]]=true;
if (i%prime[j]==0)
{
k[i*prime[j]]=k[i]/(e[i]+1)*(e[i]+2);
e[i*prime[j]]=e[i]+1;
miu[i*prime[j]]=0;
}
else
{
k[i*prime[j]]=k[i]*k[prime[j]];
e[i*prime[j]]=1;
miu[i*prime[j]]=-miu[i];
}
}
}
pre[1]=k[1];premiu[1]=1;
for (long long i=2;i<=maxn-5;i++)
{
pre[i]=pre[i-1]+k[i];
premiu[i]=premiu[i-1]+miu[i];
}
}
void work()
{
scanf("%lld%lld",&n,&m);
if (n>m) swap(n,m);
long long ans=0,r=1;
while (r<=n)
{
long long j=min((n/(n/r)),(m/(m/r)));
ans=ans+(premiu[j]-premiu[r-1])*pre[n/r]*pre[m/r];
r=j+1;
}
printf("%lld\n",ans);
}
int main()
{
scanf("%lld",&tk);
get_prime();
for (long long i=1;i<=tk;i++)
work();
return 0;
}

你可能感兴趣的:(BZOJ 3994 约数个数和)