狄利克雷卷积 以及 杜教筛学习笔记

狄利克雷卷积

文中 表示卷积 表示乘积
就是有两个积性函数 g f

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

这个就叫做狄利克雷卷积

杜教筛

假如要求积性函数 f 的前 n 项和
就要找一个积性函数 g 使得 i = 1 n g f 是个很好求得的
也就是说假如 h = g f ,那么 i = 1 n h ( i ) 这个是非常好求的,并且假设求出来就等于 A A A

然后就是杜教筛的正文了:

就是对等式的右边进行变换:

i = 1 n d | i g ( n ) f ( i d ) = i = 1 n g ( i ) j = 1 n i f ( j )

感觉这一步就是最关键的了,我看了好久都不是很懂,然后打了个表来验证
杜教筛公式验证

左边的公式感觉就是把每个数拆成他有的两个因子在那里乘,右边的公式感觉就是收集一下每个因子乘了哪些数
哇我真的是反应不过来这个啊T_T

然后后面的还好看懂,用 S ( n ) 表示 f 的前 n 项和:

= i = 1 n g ( i ) S ( n i )

然后再把 i = 1 这一项单独拿出来:
= g ( 1 ) S ( n ) + i = 2 n g ( i ) S ( n i )

这样 S ( n ) 的关系式就有了,只不过前面有个系数 g ( 1 )

再把左边那好算的一坨 i = 1 n g f 写回来,假设他算出来等于 A A A
因此就是:

A A A = g ( 1 ) S ( n ) + i = 2 n g ( i ) S ( n i )

所以最终 S ( n ) 就是:
S ( n ) = 1 g ( 1 ) ( A A A i = 2 n g ( i ) S ( n i ) )

然后如果 g ( i ) 是个常数,那么计算 S ( n i ) 的时候要 数论分块 加速一哈
就是说 n i 这个值在 [ i , n n / i ] 这段区间内都是一样的

51nod 1239 欧拉函数前缀和

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1239
好现在来套公式做题练练手:
首先要找一个合适的 g 函数,来和轻易地求出 A A A
对于欧拉函数,找的是 1 函数 1 ( n )
不懂怎么找到的,难道是凑的???
因为 φ 1 = i d
也就是说 d | n φ ( d ) 1 ( n d ) = n
也就是那个结论:

d | n φ ( d ) = n

证明阔以看 这位童鞋滴,他的第二种比较好理解
那我们的 A A A = g f = i = 1 n d | i φ ( d ) = i = 1 n i = n ( n + 1 ) 2
所以我们这里的 A A A 就是 n ( n + 1 ) 2

再来化简右边,把 g 函数换成 1 函数:

i = 1 n d | i 1 ( n ) φ ( i d )

= i = 1 n 1 ( i ) j = 1 n i φ ( j )

再把 i = 1 这一项单独弄出来
= 1 ( 1 ) S ( n ) + i = 2 n 1 ( i ) S ( n i )

1 函数是常函数一直等于1 ,就简化一下,最终就推出这个公式,左边=右边:
n ( n + 1 ) 2 = S ( n ) + i = 2 n S ( n i )

所以最终就是:
S ( n ) = n ( n + 1 ) 2 i = 2 n S ( n i )

而这种就是属于阔以分块加速的~

#include"bits/stdc++.h"
using namespace std;
const int maxn=1e6+5;
const int MOD=1e9+7;
const int inv2=5e8+4;
typedef long long LL;
bool vis[maxn];
LL phi[maxn],Sphi[maxn];//Å·À­º¯Êý£¬Å·À­º¯Êýǰ׺ºÍ 
vector<int>prime;
map<LL,LL>Mp;
void PHI(int n)
{
    memset(vis,1,sizeof(vis));
    phi[1]=1;
    Sphi[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(vis[i])
        {
            prime.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prime.size()&&i*prime[j]<=n;j++)
        {
            vis[i*prime[j]]=0;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
        Sphi[i]=(Sphi[i-1]+phi[i])%MOD;
    }
}
LL S(LL n)
{
    if(n<=maxn-5)return Sphi[n];
    if(Mp[n])return Mp[n];
    LL res=(n%MOD)*((n+1)%MOD)%MOD*inv2%MOD;
    for(LL i=2,j;i<=n;i=j+1)
    {
        j=n/(n/i);
        res-=(LL)(j-i+1)%MOD*S(n/i)%MOD;
        res%=MOD;
    }
    res=(res+MOD)%MOD;
    return Mp[n]=res;
}
int main()
{
    PHI(maxn-5);
    LL N;
    while(cin>>N)
    {
        cout<<S(N)<<endl;
    }
}

51nod 1244 莫比乌斯函数前缀和

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1244
这次选的 g 函数也是 1 函数
使用的性质是: μ 1 = e
证明阔以看这里
也就是 d | n μ ( d ) 1 ( n d ) = d | n μ ( d ) = e
这样子计算出的 A A A = i = 1 n d | i μ ( d ) = 1

那直接套公式阔以得到:

S ( n ) = 1 i = 2 n S ( n i )

51nod 1237 最大公约数之和 V3

http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1237

f ( d ) 表示 g c d ( i , j ) = d 的个数
那么其实就是求:

d = 1 n d f ( d )

而怎么求 f ( d ) 喃?很经典的一个就是用莫比乌斯反演,但是这样是 O ( n )
还有一个公式就是
2 i = 1 n φ ( i ) 1

为什么喃?
i = 1 n j = 1 i [ g c d ( i , j ) == 1 ] = φ ( i )

相当于枚举 i 然后 j 是小于 i 中与 i 互质的,那么这不就是 φ ( i ) 的个数么?
然后就是 j >= i 的时候,相当于枚举 j ,因此是两倍,最后减个 1 是因为 ( 1 , 1 ) 这个是计算进了欧拉函数中的,算了两次,所以要减去1

而我们要的是 g c d ( i , j ) = d 的,这样处理一哈就行了:

i = 1 n j = 1 n [ g c d ( i , j ) == d ] = i = 1 n d j = 1 n d [ g c d ( i , j ) == 1 ] = 2 i = 1 n d φ ( i ) 1 = 2 S ( n d ) 1

于是公式就出来了:
a n s = d = 1 n d ( 2 S ( n d ) 1 )

这样看起来好像也是 O ( n ) 的, d 要遍历 1 n ,但是我们阔以数论分块~~~
[ d , n / ( n / d ) ] 这一段内的 S ( n d ) 是相同的,而这一段的 d 也只是个等差数列非常好求,因此,就阔以写了~

#include"bits/stdc++.h"
using namespace std;
const int maxn=1e6+5;
const int MOD=1e9+7;
const int inv2=5e8+4;
typedef long long LL;
bool vis[maxn];
LL phi[maxn],Sphi[maxn];//Å·À­º¯Êý£¬Å·À­º¯Êýǰ׺ºÍ 
vector<int>prime;
map<LL,LL>Mp;
void PHI(int n)
{
    memset(vis,1,sizeof(vis));
    phi[1]=1;
    Sphi[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(vis[i])
        {
            prime.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prime.size()&&i*prime[j]<=n;j++)
        {
            vis[i*prime[j]]=0;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
        Sphi[i]=(Sphi[i-1]+phi[i])%MOD;
    }
}
LL S(LL n)
{
    if(n<=maxn-5)return Sphi[n];
    if(Mp[n])return Mp[n];
    LL res=(n%MOD)*((n+1)%MOD)%MOD*inv2%MOD;
    for(LL i=2,j;i<=n;i=j+1)
    {
        j=n/(n/i);
        res-=(LL)(j-i+1)%MOD*S(n/i)%MOD;
        res%=MOD;
    }
    res=(res+MOD)%MOD;
    return Mp[n]=res;
}
int main()
{
    PHI(maxn-5);
    LL N;
    while(cin>>N)
    {
        LL res=0;
        for(LL d=1,j;d<=N;d=j+1)
        {
            j=N/(N/d);
            LL t1=(d+j)%MOD;
            LL t2=(j-d+1)%MOD;
            LL t=t1*t2%MOD*inv2%MOD;
            res+=t%MOD*(2LL*S(N/d)-1)%MOD;
            res%=MOD;
        }
        res=(res+MOD)%MOD;
        cout<<res<<endl;
    }
}

51nod 1188 最大公约数之和 V2

题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1188

还是从最古老的公式入手,就是想求一个数m与n以内的数互质的个数:

i = 1 m [ g c d ( i , m ) == 1 ] = d | m μ ( d ) n d

m = n 时,很明显就是欧拉函数的定义,而且也退化成了一个常见的狄利克雷卷积的结论:
φ = μ i d

这是求的 g c d = 1 的方法,要是求的 g c d = d 怎么办喃,就是同时除以 d 就转换成了 g c d = 1 的了,那么 n d 内互质的个数就是 φ ( n d )

然后就是枚举因子 d 的时候只用枚举到 s q r t ( n ) ,,因为如果 d 是n的因子,那么 n d 肯定也是 n的因子,这样就不会超时了,这个很关键

因为做V2的时候脑子有点反应不过来,因此在V1这里先做个铺垫,就是求V1,如果数据范围改成1e6的话,就这个阔以改成筛法,感觉线性筛就有点考脑子了。

筛法只能过1e6的数据

#include"bits/stdc++.h"
using namespace std;
typedef long long LL;
const int maxn=1e6+5;
const int MOD=1e9+7;
LL N,M;
vector<int>prime;
int phi[maxn];
bool vis[maxn];
map<LL,LL>Mp;
LL ans[maxn];
void PHI(int n)
{
    memset(vis,1,sizeof(vis));
    phi[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(vis[i])
        {
            prime.push_back(i);
            phi[i]=i-1;
        }
        for(int j=0;j<prime.size()&&i*prime[j]<=n;j++)
        {
            vis[i*prime[j]]=0;
            if(i%prime[j]==0)
            {
                phi[i*prime[j]]=phi[i]*prime[j];
                break;
            }
            else phi[i*prime[j]]=phi[i]*(prime[j]-1);
        }
    }
}
int main()
{
    PHI(maxn-5);
    for(LL d=1;d<=maxn-5;d++)//ö¾ÙÒò×Ó 
    {
        for(LL i=1;d*i<=maxn-5;i++)
        {
            LL n=d*i;
            ans[n]+=d*phi[i];//Ï൱ÓÚÊǶÔn=d*iÕâ¸öÊýµÄ¹±Ï× 
        }
    }
    int N;
    while(cin>>N)
    {
        cout<<ans[N]<<endl;
     } 
}

理解了这个的话V2就好做了

猜你喜欢

转载自blog.csdn.net/SwustLpf/article/details/82427315
今日推荐