BZOJ 3994 [SDOI2015]约数个数和

题目大意:设d(x)为x的约数个数,给定N、M,求 这里写图片描述

完全不会,听说是莫比乌斯反演,可约数个数怎么反演…然后蒟蒻就在网上翻大神的博客,发现约数的个数具有神奇的性质:
∑ni=1∑mj=1d(ij) = ∑ni=1∑mj=1⌊n/i⌋⌊m/j⌋[gcd(i,j)==1] (不会用公式编辑器…)
然后就是莫比乌斯反演喽,gcd这种东西还是会求的。
(中间似乎还有些推导,不过还是很好推的)
最后我们发现变成了这样:
∑Nd=1μ(d)g(⌊N/id⌋)g(⌊M/jd⌋) 这个g函数是约数个数的前缀和
如何在线性筛法的过程中记录约数个数呢?我们知道线性筛法每个合数是被最小的质因数筛去的,所以用num数组记录每个数最小质因数的幂,一旦发现最小质因数就更新约数个数。(约数个数明显是积性函数)

#include
#include
#include
#include
using namespace std;
const int maxn=100000+10;
int hm[maxn],tot,mu[maxn],num[maxn],d[maxn],n,m,check[maxn];
void pre()
{
  check[1]=0;mu[1]=hm[1]=d[1]=1;
  for(int i=2;i<=50000;i++)
  {
    if(!check[i])
    {
      hm[++tot]=i;
      mu[i]=-1;
      d[i]=2;
      num[i]=1;
    }
    for(int j=1;j<=tot&&i*hm[j]<=50000;j++)
    {
      check[i*hm[j]]=1;
      if(i%hm[j]==0)
      {
        num[i*hm[j]]=num[i]+1;
        d[i*hm[j]]=d[i]/(num[i]+1)*(num[i]+2);
        break;
      }
      d[i*hm[j]]=d[i]*d[hm[j]];
      num[i*hm[j]]=1;
      mu[i*hm[j]]=-mu[i];
    }
  }
  for(int i=1;i<=50000;i++)
  {
    mu[i]+=mu[i-1];
    d[i]+=d[i-1];
    //cout<
  }
}     
int main()
{
  //freopen("3994.in","r",stdin);
  //freopen("3994.out","w",stdout);
  pre();int T;
  scanf("%d",&T);
  while(T--)
  {
    scanf("%d%d",&n,&m);
    long long res=0;
    int pre=1;
    while(pre<=min(n,m))
    {
      int v1=n/(n/pre),v2=m/(m/pre);v1=min(v1,v2);
      res=res+(long long)(mu[v1]-mu[pre-1])*(long long)d[n/pre]*(long long)d[m/pre];
      pre=v1+1;
    }
    printf("%lld\n",res);
  }
  return 0;
}

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