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

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

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

p q

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

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

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

f g ( x ) = d | x f ( d ) g ( x d )

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

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

s u m ( x ) = i n f ( i )

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

f g ( n ) = d | n g ( d ) f ( n d )

那么卷积的前缀和为:
i = 1 n f g ( i ) = i = 1 n d | i g ( d ) f ( i d )

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

= d = 1 n g ( d ) i n d f ( i ) = d = 1 n g ( d ) s u m ( n d )

在稍微变形移一下项有:

g ( 1 ) s u m ( n ) = i n f g ( i ) i = 2 n g ( i ) s u m ( n i )

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

最常见的例子是求:

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

易知有:

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

这个可以看成是 μ 函数和常数函数的卷积
所以按照上述推导的公式,我们有:
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;
}

猜你喜欢

转载自blog.csdn.net/coldfresh/article/details/81138072