狄利克雷

一大堆带着 Dirichlet 的东西。原计划是整点拉格朗日反演,但是看见 jijidawang 博客给我来劲了,遂改。

Dirichlet 前缀和

给你一个函数(或者数列) f,然后让你求 gn=d|nfd1n 的所有取值。其实就是卷个 I

当然可以枚举倍数做到 O(nlogn)。但是不够优秀。

实际上把每个质因数当成一维,然后做高维前缀和就行了。代码就这点。

for(int i=1;i<=p[0];i++){
    for(int j=1;p[i]*j<=n;j++){
        a[p[i]*j]+=a[j];
    }
}

复杂度 O(nloglogn)

类似的有 Dirichlet 差分(就是卷 μ):

for(int i=1;i<=p[0];i++){
    for(int j=n/p[i];j;j--){
        a[p[i]*j]-=a[j];
    }
}

同样有后缀和(枚举倍数求和)以及后缀差分:

for(int i=1;i<=p[0];i++){
    for(int j=n/p[i];j;j--){
        a[j]+=a[p[i]*j];
    }
}
for(int i=1;i<=p[0];i++){
    for(int j=1;j*p[i]<=n;j++){
        a[j]-=a[j*p[i]];
    }
}

P2714 四元组统计

基础应用。对于 gcd(ai,aj,ak,al)=x 的,先做个 Dirichlet 后缀和,此时所有 x 的位置变成了 x 的倍数个数,然后把 ax 变成 (ax4) ,最后 Dirichlet 后缀差分回去就可以了。

#include <iostream>
#include <algorithm>
#include <cstdio>
#define int long long
using namespace std;
int n,a[10010],p[10010];
bool v[10010];
void get(int n){
    for(int i=2;i<=n;i++){
        if(!v[i])p[++p[0]]=i;
        for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
            v[i*p[j]]=true;
            if(i%p[j]==0)break;
        }
    }
}
signed main(){
    get(10000);
    while(~scanf("%lld",&n)){
        int mx=0;
        for(int i=1;i<=n;i++){
            int x;scanf("%lld",&x);a[x]++;
            mx=max(mx,x);
        }
        for(int i=1;i<=p[0];i++){
            for(int j=mx/p[i];j;j--){
                a[j]+=a[p[i]*j];
            }
        }
        for(int i=1;i<=mx;i++)a[i]=1ll*(a[i]-3)*(a[i]-2)*(a[i]-1)*a[i]/24;
        for(int i=1;i<=p[0];i++){
            for(int j=1;j*p[i]<=mx;j++){
                a[j]-=a[p[i]*j];
            }
        }
        printf("%lld\n",a[1]);
        for(int i=1;i<=mx;i++)a[i]=0;
    }
    return 0;
}

gcd/lcm 卷积

gcd 卷积就是这个东西:

hn=gcd(i,j)=nfigj

怎么算?其实可以类似地构造 DFT 和 IDFT。事实上,我们有:

n|dhd=n|gcd(i,j)figj=n|i,n|jfigj=n|ifin|jgj

也就是后缀和。那么做一遍高维后缀和,对应点乘,然后后缀差分回去就行了。

同样的,lcm 卷积也有:

d|nhd=d|lcm(i,j)figj=d|ifid|jgj

复杂度 O(nloglogn)。讲道理我一开始看见的时候还挺震撼的,后来发现不是什么新东西了。

狄利克雷生成函数

是这个形式:

F(x)=i=1fiix

看起来很奇怪,x 在指数上。但是是为了满足如下性质:对于积性函数 f ,其狄利克雷生成函数可以表示为质数幂处取值的乘积:

F(x)=pk=0f(pk)pkx

这就比较优美。

下面是一些常见数论函数的狄利克雷生成函数:

  1. ϵ(n)=[n=1]
    显然是 1。这也是狄利克雷生成函数的单位元。
  2. ζ(n)=1
    也就是 I。它是

n=11nx

换成质数幂处乘积的形式也就是

p11px

  1. μ
    这个在质数幂处乘积表示下,k=0 时为 1,k=1 时为 1 ,其他为 0,也就是

p1px

黎曼 ζ 函数的倒数。

  1. idk(n)=nk

n=11nxk

显然是 ζ(zk)
5. φ
仍然观察质数幂处取值:

p(1+p1px+p(p1)p2x+p2(p1)p3x+)=p1xx1p1x=ζ(x1)ζ(x)

  1. σk(n)=d|ndl
    卷积一下,是 ζ(xk)ζ(x)

一个应用:筛 φμ

显然是 ζ(z1)ζ2(z)。拆开狄利克雷生成函数的一项:

(1pz)21p1z

就是幂函数的二阶差分,也就是

f(pk)={1(k=0)p2(k=1)(p1)2pk2(k2)

运算

首先是卷积。一般枚举倍数,O(nlogn)

假如说我们把 fg 卷起来,f 积性。一个加速的方法是把 f 分解成 π(n) 个只和素数 p 有关的函数的卷积,即 f(x)=pfp(x)

然后把每个函数分别卷到 g 上去,那么显然

fpg(n)=pk|nf(pk)g(npk)

那么这就是 O(nloglogn) 的了,不算求 f 的复杂度。

然后是求逆。假设 f1=g1=1fg=ϵ,给定 g,求 f

d|nf(d)g(nd)=0f(n)=d|n,dnf(d)g(nd)

当然也可以按照上边的方法 O(nloglogn) 卷积求解。

导数和积分。单项求导试试看:

(fnnx)=lnnfnnx

啊这,lnn 不是有理数。所以我们换个定义。

一般的定义是把系数 lnn 变成 n 的可重质因子个数。积分就是逆运算。这样定义就可以满足 lnab=lna+lnb 的关键性质。

对数和指数。对数仍然定义为

lnF(x)=F(x)F(x)dx

然后是指数。设 G(x)=expF(x),则 F(x)=lnG(x)=G(x)G(x)dx,于是:

F(x)G(x)=G(x)

这玩意其实可以直接算了。

下面的代码是loj6713的,是狄利克雷快速幂。

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
const int mod=998244353;
int n,k,g[1000010],f[1000010];
int qpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=1ll*ans*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return ans;
}
int p[1000010],cnt[1000010];
bool v[1000010];
void get(int n){
    for(int i=2;i<=n;i++){
        if(!v[i])p[++p[0]]=i,cnt[i]=1;
        for(int j=1;j<=p[0]&&i*p[j]<=n;j++){
            v[i*p[j]]=true;
            cnt[i*p[j]]=cnt[i]+1;
            if(i%p[j]==0)break;
        }
    }
}
void div(int f[],int g[],int n){
    int inv=qpow(g[1],mod-2);
    for(int i=1;i<=n;i++){
        f[i]=1ll*inv*f[i]%mod;
        for(int j=2;i*j<=n;j++){
            f[i*j]=(f[i*j]-1ll*f[i]*g[j]%mod+mod)%mod;
        }
    }
}
void dao(int f[],int n){
    for(int i=1;i<=n;i++)f[i]=1ll*cnt[i]*f[i]%mod;
}
void jifen(int f[],int n){
    for(int i=1;i<=n;i++)f[i]=1ll*qpow(cnt[i],mod-2)*f[i]%mod;
}
void getln(int f[],int g[],int n){
    for(int i=1;i<=n;i++)g[i]=1ll*cnt[i]*f[i]%mod;
    div(g,f,n);
    jifen(g,n);
}
void exp(int f[],int g[],int n){
    g[1]=1;
    for(int i=1;i<=n;i++){
        if(cnt[i])g[i]=1ll*qpow(cnt[i],mod-2)*g[i]%mod;
        for(int j=2;i*j<=n;j++){
            g[i*j]=(g[i*j]+1ll*f[j]*cnt[j]%mod*g[i])%mod;
        }
    }
}
int main(){
    scanf("%d%d",&n,&k);
    get(n);
    for(int i=1;i<=n;i++)scanf("%d",&f[i]);
    getln(f,g,n);int inv=qpow(k,mod-2);
    for(int i=1;i<=n;i++)g[i]=1ll*g[i]*inv%mod,f[i]=0;
    exp(g,f,n);
    for(int i=1;i<=n;i++)printf("%d ",f[i]);
    printf("\n");
    return 0;
}
posted @   gtm1514  阅读(206)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示