BZOJ3944: Sum(杜教筛模板)

BZOJ3944: Sum(杜教筛模板)


题面描述

传送门

题目分析

\(\sum_{i=1}^{n}\mu(i)\)\(\sum_{i=1}^{n}\varphi(i)\)

数据范围线性不可做。

需要使用杜教筛。

杜教筛可以在非线性时间里求出一个积性函数的前缀和。

借这里先写一些杜教筛内容。。。或许以后会补总结(雾

最开始扔积性函数:

  1. \(\mu(n)\),莫比乌斯函数
  2. \(\phi(n)\),欧拉函数。
  3. \(d(n)\),约数个数。
  4. \(\sigma(n)\),约数和函数。
  5. \(\epsilon(n)\),元函数,其值为\(\epsilon(n)=[n=1]\)
  6. \(id(n)\),单位函数,\(id(n)=n\)
  7. \(I(n)\),恒等函数,\(I(n)=1\)

先放狄利克雷卷积的式子:

假设我们现在有两个数论函数\(f,g\),则这两个函数的卷积是\((f*g)(n)=\sum_{d\mid n}f(d)·g(\frac{n}{d})\)后面的括号表示范围,一般不写的时候可以默认其为\(n\)

可以推出狄利克雷卷积满足以下运算律

  1. 交换律:\((f∗g=g∗f)\)
  2. 结合律:\(((f∗g)∗h=f∗(g∗h))\)
  3. 分配律:\(((f+g)∗h=f∗h+g∗h)\)

可以类比乘法运算律记忆。

扫描二维码关注公众号,回复: 5289681 查看本文章

那么我们可以开始搞杜教筛了。

现在我们要求一个积性函数\(f\)的前缀和,也就是\(\sum_{i=1}^{n}f(i)\)

我们尝试构造两个积性函数使\(h=f*g\)

那么我们求一下\(\sum_{i=1}^{n}h(i)\)

先记\(Sum(n)\)\(\sum_{i=1}^{n}f(i)\)
则:
\[ \sum_{i=1}^{n}h(i)=\sum_{i=1}^{n}\sum_{d\mid i}g(d)f(\frac{i}{d}) \]

然后明显可以反过来枚举。

\[ →\sum_{d=1}^{n}g(d)\sum_{d\mid i}f(\frac{i}{d}) \]

改成枚举\(\frac{i}{d}\)

\[ →\sum_{d=1}^{n}g(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i)=\sum_{d=1}^{n}g(d)S(\lfloor\frac{n}{d}\rfloor) \]

然后把式子的第一项提出来,整个代回去。

\[ \sum_{i=1}^{n}h(i)=g(1)·S(n)+\sum_{d=2}^{n}g(d)·S(\lfloor\frac{n}{d}\rfloor) \]

移项
\[ g(1)·S(n)=\sum_{i=1}^{n}h(i)-\sum_{d=2}^{n}g(d)·S(\lfloor\frac{n}{d}\rfloor) \]

这样\(g(1)\)明显为\(1\),所以这个式子就很明显了,只要\(h(i)\)的前缀和好求那么这个式子就可以在非线性时间里求出来了。

因为\(h=f*g\)我们换个形式表示上面的式子。

\[ →g(1)·S(n)=\sum_{i=1}^{n}(f*g)(i)-\sum_{d=2}^{n}g(d)·S(\lfloor\frac{n}{d}\rfloor) \]

所以只要找到一个合适的\(g\)就行了。

看个例子,我们这个题要求啥来着,\(\sum_{i=1}^{n}\mu(i)\)\(\sum_{i=1}^{n}\varphi(i)\)

先看第一个。

就不推了,根据上面那个把\(f\)换成\(\mu\)直接代到最后面。

那么应该怎么给\(g\)取值呢,我们可以简明扼要的先看一下那一项变成什么了。

\[ →\sum_{i=1}^{n}(\mu*g)(i) \]

有一个好消息,我们知道\(\mu*I=\epsilon\)。那么可以把上面的式子看成
\[ \sum_{i=1}^{n}(\mu*I)(i)=\sum_{i=1}^{n}\epsilon(i) \]

元函数的前缀和就非常好求,就是\(1\),所以我们求的答案

\[ S(n)=1-\sum_{d=2}^{n}g(d)·S(\lfloor\frac{n}{d}\rfloor) \]

再看第二个,我们还是相同的直接把\(\varphi\)代到最后面去。

则我们有式子
\[ \sum_{i=1}^{n}(\varphi*g)(i) \]

思考一下,我们记得欧拉函数有个有趣的性质\(\sum_{d|n}\varphi(d)=n\)

我们把它用卷积的形式表达,就是\(\varphi*I=id\)

带入刚才的式子里面。

\[ \sum_{i=1}^{n}(\varphi*g)(i)=\sum_{i=1}^{n}id(i) \]

明显的小高斯5岁就会的那个数列求和。。。

这个东西是\(n·(n+1)/2\)应该都知道。。。

然后代码实现的时候,可以先筛出根号范围内的答案,然后递归处理记忆化搜索。

由于需要储存下标非常大的值,所以需要使用哈希或者偷懒使用unordered_map,不要用map,会多一个log。

下面代码实测BZOJ可过,注意少开long long

是代码呢

#include <bits/stdc++.h>
#include <tr1/unordered_map>
using namespace std;
const int MAXN=4e6+7;
const int M=4e6;
#define ll long long 
bool vis[MAXN];
int mu[MAXN],sum1[MAXN];
ll phi[MAXN],sum2[MAXN];
int cnt,prime[MAXN];
tr1::unordered_map<ll,ll> w1;
tr1::unordered_map<int,short> w;
inline void get(int N)
{
    phi[1]=mu[1]=1;
    for(int i=2;i<=N;i++){
        if(!vis[i]){
            prime[++prime[0]]=i;
            mu[i]=-1;
            phi[i]=i-1;
        }
        for(int j=1;j<=prime[0];j++){
            if(i*prime[j]>N) break;
            vis[i*prime[j]]=1;
            if(i%prime[j]==0){
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            } else mu[i*prime[j]]=-mu[i],phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
    for(int i=1;i<=N;i++) sum1[i]=sum1[i-1]+mu[i],sum2[i]=sum2[i-1]+phi[i];
}
int djsmu(int x)
{
    if(x<=M) return sum1[x];
    if(w[x]) return w[x];
    int ans=1;
    for(int l=2,r;l<=x;l=r+1){
        if(r==2147483647) break;
        r=x/(x/l);
        ans-=(r-l+1)*djsmu(x/l);
    }
    return w[x]=ans;
}
ll djsphi(int x)
{
    if(x<=M) return sum2[x];
    if(w1[x]) return w1[x];
    ll ans=1ll*x*(1ll*x+1)/2;
    for(int l=2,r;l<=x&&l>=0;l=r+1){
        if(r==2147483647) break;
        r=x/(x/l);
        ans-=1ll*(r-l+1)*djsphi(x/l);
    }
    return w1[x]=ans;
}
inline int read()
{
    int x=0,c=1;
    char ch=' ';
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    while(ch=='-')c*=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*c;
}
int main()
{
    int T=read();
    get(M);
    while(T--){
        int n;
        n=read();
        printf("%lld %d\n", djsphi(n),djsmu(n));
    }
}

猜你喜欢

转载自www.cnblogs.com/victorique/p/10422968.html