SPOJ DIVCNT2(莫比乌斯反演+杜教筛)

题目传送门:http://www.spoj.com/problems/DIVCNT2/


题目分析:
千古神题,这题想了我两天也没想出来……
好吧,其实这题并没有用杜教筛,它并没有使用记忆化递归,也没有用狄利克雷卷积,只不过它的时间复杂度证明和杜教筛类似,也是 O(n23) (虽然我还是不知道具体是怎么证的)。该怎么说呢?至少加深了对积性函数前缀和的理解吧。
回到正文:


题目要我们求:

i=1nσ0(i2)

相当于求:
i=1nd|i21

如果我们改为先枚举d,接下来我们还要枚举一层什么呢?我们很容易发现,假设:
d=pk11pk22pkxx

那么它能够贡献的最小的i就是:
h(d)=i=pk1+121pk2+122pkx+12x

同时,h(d)的平方以及它的倍数的平方也会被d更新到。
于是,原式变为:
d=1n2nh(d)

然后我们再改一下枚举的顺序,我们令j=h(d),那么当我们枚举j的时候,有多少个d会满足要求呢?我们分析一下,设:
j=pk11pk22pkxx

则满足h(d)=j的d为:
d=p2k1(1)1p2k2(1)2p2kx(1)x

(-1)表示可以减一,也可以不减。
于是我们就发现了,满足条件的d有 2w(j) 个,其中w(j)表示j的不同质因数个数。
于是原式化成:
j=1n2w(j)nj

然而这还是不可做,我们又发现, 2w(j) 本质上就是指j所有约数中的无平方因数个数,而它们有一个共同的特点: μ 不为0(即为正负1),于是 μ2 等于1。原式化为:
j=1nnjd|jμ2(d)

我们令 i=jd ,然后将枚举d拉到外层,内层枚举i,得到:
d=1nμ2(d)i=1ndnid

然后我们记右边的值为 G(nd) ,并记 F(n)=nd=1μ2(d)
我们同时还发现:F(n)本质上就是1~n中无平方因子的数的个数,我们要求单独的一个F(n),可以通过一个 n 的枚举来实现:
F(n)=i=1nμ(i)ni2

至于还不知道为什么的同学可以去看看这题: bzoj2440完全平方数
而我们知道,单独求一个G(n)也是可以用 n 的时间做的。
而且我们可以用线性筛在O(n)的时间内求出1~n的所有F值(求个 μ 再平方再前缀和),G值(即约数个数的前缀和),并O(1)解决询问。
现在我们看回原式:
d=1nμ2(d)G(nd)

我们对G进行下底函数分块,假设 nd 相等的一段是[L,R],那就对答案产生贡献:(F(R)-F(L-1))*G( nd )。
之前说过,我们可以通过预处理达到O(1)返回这三个值,也可以用 n 的时间计算这些值,于是我们要在两者之间寻找一个平衡。由类似杜教筛时间复杂度证明可知,当我们预处理 n23 的时候,时间复杂度最优,达到 O(n23)


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=1e8;
const int maxt=10010;
typedef long long LL;

bool vis[maxn];
int prime[maxn/10];
int cur=0;

int miu[maxn];
LL f[maxn];
int pk[maxn];
LL g[maxn];

LL que[maxt];
int t;
LL n=0,N;

void Preparation()
{
    miu[1]=1,g[1]=1;
    for (int i=2; i<N; i++)
    {
        if (!vis[i]) prime[++cur]=i,miu[i]=-1,pk[i]=1,g[i]=2;
        for (int j=1; j<=cur && i*prime[j]<N; j++)
        {
            int k=i*prime[j];
            vis[k]=true;
            if (i%prime[j]) miu[k]=-miu[i],pk[k]=1,g[k]=2*g[i];
            else
            {
                miu[k]=0;
                pk[k]=pk[i]+1;
                g[k]=g[i]/pk[k]*(pk[k]+1);
                break;
            }
        }
    }
    for (int i=1; i<N; i++) f[i]=miu[i]*miu[i];
    for (int i=2; i<N; i++) f[i]+=f[i-1],g[i]+=g[i-1];
}

LL G(LL x)
{
    if (x<N) return g[x];
    LL temp=0,last;
    for (LL i=1; i<=x; i=last+1)
    {
        last=x/(x/i);
        temp+=( x/i*(last-i+1LL) );
    }
    return temp;
}

LL F(LL x)
{
    if (x<N) return f[x];
    int sx=(int)floor( sqrt( (double)x )+0.0000001 );
    LL temp=0;
    for (int i=1; i<=sx; i++)
        temp+=( (long long)miu[i]*( x/( (long long)i*(long long)i ) ) );
    return temp;
}

int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);

    scanf("%d",&t);
    for (int i=1; i<=t; i++)
    {
        cin>>que[i];
        n=max(n,que[i]);
    }
    if (n<=10000) N=n+1;
    else N=maxn;

    Preparation();

    for (int q=1; q<=t; q++)
    {
        n=que[q];
        LL ans=0,last;
        for (LL i=1; i<=n; i=last+1)
        {
            last=n/(n/i);
            ans+=( G(n/i)*( F(last)-F(i-1) ) );
        }
        cout<<ans<<endl;
    }

    return 0;
}
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值