bzoj 4176 Lucas的数论 题解【莫比乌斯反演】【杜教筛】

作者: wjyyy 分类: 数学,杜教筛,狄利克雷卷积,莫比乌斯反演,解题报告 发布时间: 2019-04-13 20:13

点击量:49

怎么又有我不知道的东西啊..自闭了

Description

去年的 Lucas 非常喜欢数论题,但是一年以后的 Lucas 却不那么喜欢了。

在整理以前的试题时,发现了这样一道题目“求 $\sum f(i)$,其中 $1\le i\le n$”,其中 $f(i)$ 表示i的约数个数。他现在长大了,题目也变难了。

求如下表达式的值:
$$
\sum_{i=1}^n\sum_{j=1}^nf(ij)
$$
其中 $f(ij)$ 表示 $ij$ 的约数个数。

他发现答案有点大,只需要输出模 $1000000007$ 的值。

Input

第一行一个整数 $n$。

Output

一行一个整数 $ans$,表示答案模 $1000000007$ 的值。

Sample Input

2

Sample Output

8

HINT

对于 $100\%$ 的数据 $n\le 10^9$。

题解:

这个题是 SDOI2015 约数个数和(题面 题解) 的升级版。

首先化式子,根据上一道题推出的结论
$$
\begin{aligned}
&\sum_{i=1}^n\sum_{j=1}^nd(ij)\\
=&\sum_{i=1}^n\sum_{j=1}^n\sum_{x|i}\sum_{y|j}[\gcd(x,y)=1]
\end{aligned}
$$
(题面中的 $f$ 已转成 $d$)令
$$
f(k)=\sum_{i=1}^n\sum_{j=1}^n\sum_{x|i}\sum_{y|j}[\gcd(x,y)=k]\\
\begin{aligned}
g(k)&=\sum_{k|d}f(d)\\
&=\sum_{i=1}^n\sum_{j=1}^n\sum_{x|i}\sum_{y|j}[k|\gcd(x,y)]\\
&=\sum_{i=1}^\left\lfloor\frac nk\right\rfloor\sum_{j=1}^\left\lfloor\frac nk\right\rfloor\sum_{x|i}\sum_{y|j}[1|\gcd(x,y)]\\
&=\sum_{i=1}^\left\lfloor\frac nk\right\rfloor\sum_{j=1}^\left\lfloor\frac nk\right\rfloor d(i)d(j)
\end{aligned}
\\
\begin{aligned}
f(k)&=\sum_{k|d}\mu\left(\frac dk\right)g(d)\\
&=\sum_{i=1}^\left\lfloor\frac nk\right\rfloor\mu(i)g(ki)
\end{aligned}
$$
定义 $sumd(k)=\sum_{i=1}^kd(i)$,则 $g(k)=sumd^2\left(\left\lfloor \frac nk\right\rfloor\right)$。因为我们要求的答案是 $f(1)$,所以代入 $k=1$,答案是
$$
\sum_{i=1}^n\mu(i)sumd^2\left(\left\lfloor\frac ni\right\rfloor\right)
$$
现在我们可以 $O(n)$ 求它了。

但是这还不够。看上去可以数论分块把枚举的复杂度降低到 $O(\sqrt n)$,$\mu(i)$ 可以杜教筛。但是我们不知道计算 $sumd$ 的复杂度。

//以下部分感谢 _rqy 的指导

根据定义,我们把 $sumd$ 拆开:
$$
\begin{aligned}
sumd(k)&=\sum_{i=1}^k\sum_{j|i}1\\
&=\sum_{j=1}^k\sum_{j|i}1\\
&=\sum_{j=1}^k\left\lfloor\frac kj\right\rfloor
\end{aligned}
$$
因此计算 $sumd(k)$ 的复杂度是 $O(\sqrt k)$ 的,如果利用杜教筛的预处理+记忆化,计算 $\forall 1\le i\le n,sumd(i)$ 的时间复杂度是 $O\left(n^\frac 23\right)$。

因此总时间复杂度为 $O\left(n^\frac 23\right)$。

在这里留下一个杜教筛的查错备忘,因为小数据(能手算的)会直接通过形如 if(x<=3000000) 这种判断跑过去,因此线筛写对了就可以过样例。样例一定要把 if 删了多跑几遍,而且这种题对拍应该挺方便。

Code:

#include<cstdio>
#include<cstring>
#define ll long long
#define p 1000000007
int mu[3000100],gmu[5000];
ll d[3000100],f[3000100],gd[5000];
bool is[3000100],usedmu[5000],usedd[5000];
int pri[3000100],cnt=0,n;
int calcmu(int x)
{
    if(x<=3000000)
        return mu[x];
    int t=n/x;
    if(usedmu[t])
        return gmu[t];
    usedmu[t]=1;
    ll ans=1;
    for(int i=2;i<=x;++i)
    {
        int nxt=x/(x/i);
        ans=(ans-(ll)calcmu(x/i)*(nxt-i+1)%p+p)%p;
        i=nxt;
    }
    return gmu[t]=ans;
}
int calcd(int x)
{
    if(x<=3000000)
        return d[x];
    int t=n/x;
    if(usedd[t])
        return gd[t];
    usedd[t]=1;
    ll ans=0;
    for(int i=1;i<=x;++i)
    {
        int nxt=x/(x/i);
        ans=(ans+(ll)x/i*(nxt-i+1))%p;
        i=nxt;
    }
    return gd[t]=ans;
}
int main()
{
    is[0]=is[1]=1;
    mu[1]=1;
    d[1]=1;
    for(int i=2;i<=3000000;++i)
    {
        if(!is[i])
        {
            pri[++cnt]=i;
            mu[i]=-1;
            f[i]=2;
            d[i]=2;
        }
        for(int j=1;j<=cnt&&i*pri[j]<=3000000;++j)
        {
            is[i*pri[j]]=1;
            if(i%pri[j])
            {
                mu[i*pri[j]]=-mu[i];
                f[i*pri[j]]=2;
                d[i*pri[j]]=2*d[i];
            }
            else
            {
                mu[i*pri[j]]=0;
                f[i*pri[j]]=f[i]+1;
                d[i*pri[j]]=d[i]/f[i]*f[i*pri[j]];
                break;
            }
        }
        d[i]=(d[i-1]+d[i])%p;
        mu[i]=((mu[i-1]+mu[i])%p+p)%p;
    }
    scanf("%d",&n);
    ll ans=0;
    for(int i=1;i<=n;++i)
    {
        int nxt=n/(n/i);
        ll k=calcd(n/i);
        ans=(ans+k*k%p*(calcmu(nxt)-calcmu(i-1))%p+p)%p;
        i=nxt;
    }
    printf("%lld\n",ans);
    return 0;
}

说点什么

avatar
  Subscribe  
提醒
/* */