今天让我们来学一学杜教筛

终于想起来要学这个了,但是似乎还没学完?
先写一部分那就。

注意:在此讨论的函数的定义域均在正整数上讨论
积性函数定义:
设有 f(x) f ( x ) 满足:若

pq p 和 q 为 质 数

则有
f(pq)=f(q)f(p) f ( p ∗ q ) = f ( q ) ∗ f ( p )

那么我们称 f(x) f ( x ) 为积性函数。
如:
1.常数函数 l(n)=1 l ( n ) = 1
2. d(n) d ( n ) :n的因子个数之和。
3. σ(n) σ ( n ) :n的各因子之和。
4. μ(n) μ ( n ) :莫比乌斯函数。
5. ϕ(n) ϕ ( n ) :欧拉函数。

狄利克雷卷积
设有函数 f(x) f ( x ) g(x) g ( x ) ,其狄利克雷卷积为:

fg(x)=d|xf(d)g(xd) f ∗ g ( x ) = ∑ d | x f ( d ) ∗ g ( x d )

性质:两个积性函数的狄利克雷卷积也是积性的。

杜教筛
作用:在 O(n23) O ( n 2 3 ) 的时间里求某些积性函数的前缀和
即:

sum(x)=inf(i) s u m ( x ) = ∑ i n f ( i )

所以显而易见所能处理的n的数量级就可增加一些了。
来看一一下具体是怎么操作的。
设我们要求 sum(x)=nif(i) s u m ( x ) = ∑ i n f ( i ) f(n) f ( n ) 是积性函数。
那么我们找到一个同样是积性的函数 g(n) g ( n ) f(n) f ( n ) 卷一卷。
有:

fg(n)=d|ng(d)f(nd) f ∗ g ( n ) = ∑ d | n g ( d ) f ( n d )

那么卷积的前缀和为:
i=1nfg(i)=i=1nd|ig(d)f(id) ∑ i = 1 n f ∗ g ( i ) = ∑ i = 1 n ∑ d | i g ( d ) f ( i d )

变换枚举角度为:
=dg(d)d|inf(id) = ∑ d g ( d ) ∑ d | i n f ( i d )

=d=1ng(d)indf(i)=d=1ng(d)sum(nd) = ∑ d = 1 n g ( d ) ∑ i n d f ( i ) = ∑ d = 1 n g ( d ) s u m ( ⌊ n d ⌋ )

在稍微变形移一下项有:

g(1)sum(n)=infg(i)i=2ng(i)sum(ni) g ( 1 ) s u m ( n ) = ∑ i n f ∗ g ( i ) − ∑ i = 2 n g ( i ) s u m ( ⌊ n i ⌋ )

我们的目标是求 sum(n) s u m ( n ) ,假如卷积函数的前缀和很好求的话,那么我们只需要处理最右边那个东西了。

最常见的例子是求:

sum(n)=i=1nμ(i) s u m ( n ) = ∑ i = 1 n μ ( i )

易知有:

d|nμ(d)=1[n==1] ∑ d | n μ ( d ) = 1 ∗ [ n == 1 ]

这个可以看成是 μ μ 函数和常数函数的卷积
所以按照上述推导的公式,我们有:
sum(n)=1i=2sum(ni) s u m ( n ) = 1 − ∑ i = 2 s u m ( ⌊ n i ⌋ )

看这个式子的结构就具有递归性,右边的那个就可以分块处理了。
来一发板子题。Sum HYSBZ - 3944
就是求欧拉函数和莫比乌斯函数的前缀和,n的范围是int范围。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define maxx 2500005
using namespace std;
int prime[maxx],cnt;
bool p[maxx];
long long mu[maxx],phi[maxx];
map<long long ,long long>M,P;//用map来记忆化
void init()
{
    p[1]=mu[1]=phi[1]=1;
    for(int i=2;i<maxx;i++)
    {
        if(!p[i])
        {
            prime[cnt++]=i;
            mu[i]=-1;
            phi[i]=i-1;
        }
        for(int j=0;j<cnt&&i*prime[j]<N;j++)
        {
            p[i*prime[j]]=true;
            if(i%prime[j])
            {
                mu[i*prime[j]]=-mu[i];
                phi[i*prime[j]]=phi[i]*phi[prime[j]];
            }
            else
            {
                mu[i*prime[j]]=0;
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
        }
    }
    /*for(int i=1;i<100;i++)
        cout<<i<<" "<<phi[i]<<endl;*/
    for(int i=1;i<N;i++)
        mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}
long long Phi(long long x)
{
    if(x<N)return phi[x];
    if(P[x])return P[x];
    long long res=0;
    for(long long i=2,last;i<=x;i=last+1)
    {
        last=x/(x/i);
        res+=(last-i+1)*Phi(x/i);
    }
    return P[x]=x*(x+1)/2-res;
}
long long Mu(long long x)
{
    if(x<N)return mu[x];
    if(M[x])return M[x];
    long long res=0;
    for(long long i=2,last;i<=x;i=last+1)
    {
        last=x/(x/i);
        res+=(last-i+1)*Mu(x/i);
    }
    return M[x]=1-res;
}
int main()
{
    init();
    int t;
    cin>>t;
    while(t--)
    {
        int n;
        scanf("%d",&n);
        printf("%lld %lld\n",Phi(n),Mu(n));
    }
    return 0;
}

但是这种写法T了…….应该是记忆化的问题。所以这个记忆化得在处理一下。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define maxx 2500005
using namespace std;
int prime[maxx],cnt;
bool p[maxx];
long long mu[maxx],phi[maxx];
long long ans1[maxx],ans2[maxx];
long long n;
void init()
{
    p[1]=mu[1]=phi[1]=1;
    for(int i=2;i<maxx;i++)
    {
        if(!p[i])
        {
            prime[cnt++]=i;
            mu[i]=-1;
            phi[i]=i-1;
        }
        for(int j=0;j<cnt&&i*prime[j]<maxx;j++)
        {
            p[i*prime[j]]=true;
            if(i%prime[j])
            {
                mu[i*prime[j]]=-mu[i];
                phi[i*prime[j]]=phi[i]*phi[prime[j]];
            }
            else
            {
                mu[i*prime[j]]=0;
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
        }
    }
    /*for(int i=1;i<100;i++)
        cout<<i<<" "<<phi[i]<<endl;*/
    for(int i=1;i<maxx;i++)
        mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}
void work(long long x)
{
    if(x<maxx)return ;
    int t=n/x;
    if(p[t])return ;
    p[t]=true;
    ans1[t]=(long long)x*(x+1)/2;
    ans2[t]=1;

    for(long long i=2,last;i<=x;i=last+1)
    {
        last=x/(x/i);
        if(x/i<maxx)
        {
            ans1[t]-=(last-i+1)*phi[x/i];
            ans2[t]-=(last-i+1)*mu[x/i];
        }
        else
        {
            work(x/i);
            ans1[t]-=(last-i+1)*ans1[n/(x/i)];
            ans2[t]-=(last-i+1)*ans2[n/(x/i)];
        }
    }
}
int main()
{
    init();
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%lld",&n);
        if(n<maxx)
            printf("%lld %lld\n",phi[n],mu[n]);
        else
        {
            work(n);
            printf("%lld %lld\n",ans1[1],ans2[1]);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值