【学习笔记】狄利克雷卷积与高级筛法

Page Views Count

狄利克雷卷积#

概念#

对于数论函数 f,g,定义其狄利克雷卷积 h=fg,满足:

h(n)=(fg)(n)=dnf(d)g(nd)

运算律:

  • 满足交换律,显然具有对称性。

  • 满足结合律,等价于三个 di 贡献到 n

  • 满足加法的分配率。

常见数论函数:

  • I,常函数,值恒为 1

  • Idk,幂函数,值为 nk

  • ϵ,狄利克雷卷积的单位元,除 ϵ(1)=1 外其余均为 0

  • μ,莫比乌斯函数,狄利克雷卷积的逆元。

  • φ,欧拉函数。

  • σk,除数函数,定义 σk(n)=dndk

常见数论函数卷积变换#

  • μI=ϵ

  • φI=Id

  • IdkI=σk

  • μId=φ

  • (μIdk)Idk=ϵ

  • (φIdk)Idk=Idk+1

杜教筛#

原理#

求数论函数前缀和。

h=fg,S=f,容易推得:

i=1nh(i)=i=1njif(j)g(ij)=j=1ng(j)i=1n/if(i)=i=1ng(i)Sf(ni)

整理一下可以写成:

Sf(n)=i=1nh(i)i=2ng(i)Sf(ni)

如果构造出可以快速计算前缀和的函数 g,h,就可以递归求解了。

时间复杂度#

首先需要证明的是:

nab=nab

RHS=k,则有:

k×abn<(k+1)×ab

于是:

k×bna<(k+1)×b

从而:

k×bna<(k+1)×b

knab<k+1

即:

nab=nab


这说明当我们数论分块向下递归时,递归到 n=nd,枚举 d 得到 nd=ndd,一定可以表示为 nk 的形式,而这一定在第一层递归时处理到(不考虑先后顺序),于是在记忆化的加持下,计算复杂度只需要考虑第一层递归即可。

设当前块长 i,对复杂度的贡献就是 O(ni)

i>n 时,第二层最大 O(n1/4),所以总复杂度 O(n3/4)

in 时,就写成:

T(n)=O(i=1nni)=O(n1n1xdx)=O(n3/4)

但较小的前缀和是可以预处理出的,假设预处理出了 [1,nk] 的前缀和,那么也就是只有 ni>nk 的部分才需要递归,也就变成:

T(n)=O(nk+i=1n1kni)=O(nk+n1n1k1xdx)=O(nk+n1k/2)

k=23 得到理论最优复杂度 O(n2/3)

数论分块时(无论嵌套几层)使用杜教筛求数论函数前缀和,由于所有询问的区间右端点为 nnd,可以表示为上述记忆化的形式,所以并不会增加复杂度。

点击查看代码

namespace Phi{
    int pr[maxn],mn[maxn],mnpw[maxn],phi[maxn];
    bool vis[maxn];
    ull s1[maxn],s2[maxn];
    bool mark[maxn];
    inline void linear_sieve(){
        mn[1]=1,phi[1]=1;
        for(int i=2;i<=lim;++i){
            if(!vis[i]) pr[++pr[0]]=i,mn[i]=i,mnpw[i]=i,phi[i]=i-1;
            for(int j=1;j<=pr[0]&&i*pr[j]<=lim;++j){
                vis[i*pr[j]]=1,mn[i*pr[j]]=pr[j];
                if(mn[i]==pr[j]) mnpw[i*pr[j]]=mnpw[i]*pr[j];
                else mnpw[i*pr[j]]=pr[j];
                if(i*pr[j]==mnpw[i*pr[j]]) phi[i*pr[j]]=phi[i]*pr[j];
                else phi[i*pr[j]]=phi[i*pr[j]/mnpw[i*pr[j]]]*phi[mnpw[i*pr[j]]];
                if(i%pr[j]==0) break;
            }
        }
        for(int i=1;i<=lim;++i) s1[i]=s1[i-1]+phi[i];
    }
    ull get_sum(int N){
        if(N<=lim) return s1[N];
        if(mark[n/N]) return s2[n/N];
        mark[n/N]=1;
        ull sum=0;
        for(unsigned int l=2,r;l<=N;l=r+1){
            r=N/(N/l);
            sum+=1ull*(r-l+1)*get_sum(N/l);
        }
        if(N&1) sum=1ull*(N+1)/2*N-sum;
        else sum=1ull*N/2*(N+1)-sum;
        return s2[n/N]=sum;
    }
    inline ull solve(){
        memset(mark,0,sizeof(mark));
        return get_sum(n);
    }
}

namespace Mu{
    int pr[maxn],mu[maxn];
    bool vis[maxn];
    int s1[maxn],s2[maxn];
    bool mark[maxn];
    inline void linear_sieve(){
        mu[1]=1;
        for(int i=2;i<=lim;++i){
            if(!vis[i]) pr[++pr[0]]=i,mu[i]=-1;
            for(int j=1;j<=pr[0]&&i*pr[j]<=lim;++j){
                vis[i*pr[j]]=1,mu[i*pr[j]]=-mu[i];
                if(i%pr[j]==0){
                    mu[i*pr[j]]=0;
                    break;
                }
            }
        }
        for(int i=1;i<=lim;++i) s1[i]=s1[i-1]+mu[i];
    }
    int get_sum(int N){
        if(N<=lim) return s1[N];
        if(mark[n/N]) return s2[n/N];
        mark[n/N]=1;
        ull sum=0;
        for(unsigned int l=2,r;l<=N;l=r+1){
            r=N/(N/l);
            sum+=(r-l+1)*get_sum(N/l);
        }
        sum=1-sum;
        return s2[n/N]=sum;
    }
    inline int solve(){
        memset(mark,0,sizeof(mark));
        return get_sum(n);
    }
}

常用构造#

最常用的是 μI=ϵ 以及 φI=Id

在此基础上有:(μIdk)Idk=ϵ(φIdk)Idk=Idk+1

另外根据 σk 的定义有 IdkI=σk

例题#

Luogu-P5218 无聊的水题 II#

容易发现是要求选出一个集合满足 gcd=1

F(n) 为值域 [1,n] 的方案数,那么 gcd=k 就是 F(nk),可以单步容斥。

F(n)=2n1k=2nF(nk)

式子长得像杜教筛,复杂度 O(n3/4) 不太好过。

考虑设 f(d)gcd=d 的方案数,g(d)dgcd 的方案数,则 g(d)=2n/d1,且有:

g(d)=dnf(n)

根据莫比乌斯反演有:

f(d)=dnμ(nd)g(n)

所以:

f(1)=i=1nμ(i)g(i)=i=1nμ(i)(2n/i1)

杜教筛预处理+数论分块即可,使用光速幂复杂度 O(n2/3)

LibreOJ-6229 这是一道简单的数学题#

j 的上下界改成 [1,n],处理一下 i=j 的情况即可。

简单反演得到:

T=1ndTμ(d)d2S1(nd)2

S1(x)1 次自然数幂和。

于是要求 dnμ(d)d2 的前缀和,不难发现这个函数是 (μId2)I,根据狄利克雷卷积的交换律以及结合律,可以得到 (μId2)IId2=(μId2)Id2I=ϵI=I

于是有:

Sf(n)=ni=2ni2Sf(ni)

杜教筛处理即可。

LibreOJ-6491 XXOI2018 简单的最大公约数#

和上面的题差不多,先设 f(m) 表示值域 [1,m]n 个数 gcd=1 的方案数,答案是 k=1mk×f(mk),最终求解用数论分块。

f(m)=mni=2mf(mi)

直接算还是 O(n3/4)

希望预处理一些 f

依旧是莫比乌斯反演,把 f(m,k) 扩充到值域 [1,m]gcd=k 的方案数,g(m,k) 定义为值域 [1,m]kgcd 的方案数,于是反演得到:

g(m,k)=mkn

f(m,k)=kmμ(mk)mmn

只关心 k=1,所以有:

f(m)=k=1mμ(k)mkn

这个直接求一行比较困难,考虑差分:

Δf(m)=f(m)f(m1)=k=1mμ(k)(mknm1kn)

分析一下什么时候 mkm1k,显然一定是 km,即 m 恰好到了下一个块里。

于是这个预处理就是调和级数 O(nlogn) 的,由于 μ 的性质,实际产生贡献的会更少。

这样大致处理 2×106 左右,再套类似杜教筛的东西就可以通过了。


另一个做法是使用欧拉反演,即利用 φI=Id 的性质,可以得到:

i1=1mi2=1min=1mgcd(i1,i2,,in)=i1=1mi2=1min=1mdgcd(i1,i2,,in)φ(d)=d=1mφ(d)mdn

杜教筛朴素计算即可。

PN 筛#

Powerful Number#

定义一个数 n 是 Powerful Number(PN) 当且仅当 n=pPpici,ci>1

每个 PN 一定可以被唯一表示为 a2b3 的形式,次数为偶数的放在 a 部分,次数为奇数的将 3 次放在 b 部分,剩余放在 a 部分。

于是可以通过枚举 a 的值来计算 PN 的个数,大致是:

O(i=1nni23)=O(1nni23)=O(n)

所以可以通过 DFS 来得出所有 PN。

求积性函数前缀和#

模板题 为例。

给定积性函数 f,求其前缀和。

考虑构造积性函数 g,满足 g(p)=f(p),这里 g(p)=f(p)=p(p1),可以构造 g=φId

再找到积性函数 h,满足 f=gh,由于 f(p)=g(1)h(p)+g(p)h(1),积性函数 1 处点值一定为 1,所以 f(p)=h(p)+g(p),即 h(p)=0,这样所有可以被质因子筛到的点值处都是 0,也就是所有非 PN 点值都是 0

这样化简 f 前缀和的式子:

i=1nf(i)=i=1njig(i)h(ij)=i=1nh(i)Sg(ni)=i=1i is PNnh(i)Sg(ni)

g 的前缀和可以用杜教筛求出(有更优秀的也可以),计算答案在 DFS 求出所有 PN 时进行,其中求 h 可以求出所有 h(pc) 再累乘。

h(pc) 最常规的办法是利用 f=gh,移项得到 h(pc)=f(pc)i=0c1h(pi)g(pci)

点击查看代码
ll n;
int pr[maptrn],phi[maptrn];
bool vis[maptrn];
int s1[maptrn],s2[maptrn];
bool mark[maptrn];
int pw[maptrn/10][40],h[maptrn/10][40];
inline void linear_sieve(){
    phi[1]=1;
    for(int i=2;i<=lim;++i){
        if(!vis[i]) pr[++pr[0]]=i,phi[i]=i-1;
        for(int j=1;j<=pr[0]&&i*pr[j]<=lim;++j){
            vis[i*pr[j]]=1,phi[i*pr[j]]=phi[i]*phi[pr[j]];
            if(i%pr[j]==0){
                phi[i*pr[j]]=phi[i]*pr[j];
                break;
            }        
        }
    }
    for(int i=1;i<=lim;++i) s1[i]=(s1[i-1]+1ll*i*phi[i]%mod)%mod;
    for(int i=1;i<=pr[0];++i){
        pw[i][0]=1,h[i][0]=1;
        for(ll j=pr[i],e=1;;j*=pr[i],++e){
            pw[i][e]=j%mod;
            int now=1ll*pw[i][e]*(pw[i][e]-1)%mod;
            for(int k=0;k<e;++k){
                now=(now-1ll*h[i][k]*(pr[i]-1)%mod*pw[i][e-k-1]%mod*pw[i][e-k]%mod+mod)%mod;
            }
            h[i][e]=now;
            if(j>n/pr[i]) break;
        }
    }
}
int get_sumg(ll N){
    if(N<=lim) return s1[N];
    if(mark[n/N]) return s2[n/N]; 
    mark[n/N]=1;
    int tmpn=N%mod;
    int res=1ll*tmpn*(tmpn+1)%mod*(2ll*tmpn%mod+1)%mod*inv6%mod;
    for(ll l=2,r;l<=N;l=r+1){
        r=N/(N/l);
        int tmpl=(l-1)%mod,tmpr=r%mod;
        int now=(1ll*tmpr*(tmpr+1)/2-1ll*tmpl*(tmpl+1)/2)%mod;
        res=(res-1ll*now*get_sumg(N/l)%mod+mod)%mod;
    }
    return s2[n/N]=res;
}
int ans;
void dfs(ll N,int ptr,int res){
    ans=(ans+1ll*res*get_sumg(n/N)%mod)%mod;
    if(ptr>pr[0]) return;
    for(int i=ptr;i<=pr[0];++i){
        if(N>n/(1ll*pr[i]*pr[i])) break;
        for(ll j=N*pr[i]*pr[i],e=2;;j*=pr[i],++e){
            dfs(j,i+1,1ll*res*h[i][e]%mod);
            if(j>n/pr[i]) break;
        }
    }
}

int main(){
    scanf("%lld",&n);
    linear_sieve();
    dfs(1,1,1);
    printf("%d\n",ans);
    return 0;
}

复杂度分析#

g 前缀和复杂度认为是杜教筛复杂度 O(n2/3)

只预处理 [1,n] 的质数,每个质数枚举 pc 并计算 h 的复杂度是 O(log2n),素数密度 π(n)=O(nlogn),所以总复杂度是 O(nlogn),实际这个上界比较松。

于是得到大致是 O(n2/3) 的复杂度。

算法流程总结#

  1. 用积性函数拟合出合适的 g

  2. 构造快速求 g 前缀和的方法。

  3. 预处理出 h(pc)

  4. DFS 出所有 PN 并计算答案。

例题#

LibreOJ-6053 简单的函数#

除了 2 以外的 p 处点值是 p1,先不考虑特例的话拟合成 φ 是非常好的,在此基础上 φ(2)=1,所以将 2 的倍数处拟合成 3φ。(肯定不是 φ+2 吧)

g(n)={3φ(n)2nφ(n)2n

φ 提出一个会好一点,顺便把系数也带出来:

l(n)={φ(n)2n02n

于是 g(n)=φ(n)+2l(n),也即 Sg(n)=Sφ(n)+2Sl(n)

注意到 Sl(n) 有意义的 n 都是偶数,可以用一个 S(n)=i=1nφ(2i) 来代替,即 Sl(n)=S(n2)

φ(2n) 详写成关于 n 的:

φ(2n)={2φ(n)2nφ(n)2n

参照上面的化简方法,得到:

φ(2n)φ(n)={φ(n)2n02n

这个和 l 一模一样,这里当然再把 Sl 换成 S

S(n)=Sφ(n)+S(n2)

放回最初的式子 Sg(n)=Sφ(n)+2Sh(n)=Sφ(n)+2S(n2),这样一直把 S 展开,就是一个倍增的形式,单次计算是 O(logn),所求点值都在杜教筛射程范围内。

构造的另一函数 h 就是朴素的 O(nlogn) 预处理。

根据 PN 的数量级和预处理的复杂度,总体仍是 O(n2/3)

Min_25 筛#

Min_25 筛是一种基于埃筛的筛法,可以对数论函数 f(n) 求前缀和,要求 f(p) 是关于 p 的低次多项式,且 f(pk) 可快速计算。

构造质数点值前缀和函数#

在求解的开始,先用线性筛筛出小于等于 n 的质数,定义 minp(i) 表示 i 的最小质因子。

g(n)=pP,pnf(p),这个式子不好直接求,先设 gk(n)=pP,pnpk,即 g(n)=kakgk(n),其中 akf(p) 这个关于 p 的低次多项式 k 次项系数。

gk(n,j)=i=1nik[iPminp(i)>pj],这个定义相当于埃筛筛掉了含小于等于 pj 质因子的合数,容易发现找到最小的质数 px 满足 pxn,那么 gk(n)=gk(n,x),因为此时不再有最小值因子大于 px 的合数。

考虑递推,有:

gk(n,j)={gk(n,j1)pj2ngk(n,j1)pjk(g(npj,j1)g(pj1,j1))pj2<n

第二个递推式实际上就是把 minp(i)=pj 的数拆成 pj 和另一部分的乘积,这一部分就表现在 g(npj,j1) 中,但这里面还包含了质数,所以要减去。

于是只关心 O(n) 个块筛位置的点值,而 pj1n,所以一定可以被块筛筛到,这样计算复杂度也是经典方法了,具体是:

O(i=1nπ(i)+i=1nπ(ni))=O(i=1nπ(ni))=O(1nnxlognxdx)=O(n1n1xdxlogn)=O(n3/4logn)

构造前缀和函数#

方法一:递归#

S(n)=inf(i),仿照上面,扩充 S 的定义为 S(n,j)=inf(i)[minp(i)>pj],这样答案就是 S(n,0)+1

直接递归,式子是:

S(n,j)=g(n)g(pj)+pj<pknpk2pke+1nf(pke)S(npke,k)+f(pke+1)

就是分质数合数来计算,后半部分相当于枚举了一个最小值因子及幂次,然后递归求解,f(pke+1) 的加入源于 S 并不计算 1 的点值,因此要补充计算,而 f(pk) 已经在质数处算过了。

复杂度证明见论文,结论是复杂度为 O(n1ϵ),但 n1013 时为 O(n3/4logn)

实现常数很小,但只能求单点。

点击查看代码
inline int S1(ll N){
    N%=mod;
    return N*(N+1)%mod*inv2%mod;
}
inline int S2(ll N){
    N%=mod;
    return N*(N+1)%mod*(2*N+1)%mod*inv6%mod;
}
inline int f(ll N){
    N%=mod;
    return N*(N-1+mod)%mod;
}

ll n;
int lim;
int pr[maxn],cntpr;
bool vis[maxn];
ll id[maxn<<1];
int id1[maxn],id2[maxn];
int g1[maxn<<1],g2[maxn<<1];

inline int get_id(ll N){
    if(N<=lim) return id1[N];
    else return id2[n/N];
}

inline void linear_sieve(){
    for(int i=2;i<=lim;++i){
        if(!vis[i]) pr[++cntpr]=i;
        for(int j=1;j<=cntpr&&i*pr[j]<=lim;++j){
            vis[i*pr[j]]=1;
            if(i%pr[j]==0) break;
        }
    }
}

int S(ll N,int j){
    if(pr[j]>=N) return 0;
    int res=0;
    res=(g1[get_id(N)]-g1[get_id(pr[j])]+mod)%mod;
    for(int k=j+1;k<=cntpr&&1ll*pr[k]*pr[k]<=N;++k){
        ll now=pr[k];
        for(int e=1;now<=N;++e,now*=pr[k]){
            res=(res+1ll*f(now)*(S(N/now,k)+(e>1))%mod)%mod;
        }
    }
    return res;
}

int main(){
    scanf("%lld",&n);
    lim=sqrt(n)+1;
    linear_sieve();
    for(ll l=1,r;l<=n;l=r+1){
        r=n/(n/l);
        id[++id[0]]=n/l;
        if(n/l<=lim) id1[n/l]=id[0];
        else id2[n/(n/l)]=id[0];
        g1[id[0]]=(S1(n/l)-1+mod)%mod,g2[id[0]]=(S2(n/l)-1+mod)%mod;
    }
    for(int j=1;j<=cntpr;++j){
        for(int i=1;i<=id[0]&&1ll*pr[j]*pr[j]<=id[i];++i){
            g1[i]=(g1[i]-1ll*pr[j]*(g1[get_id(id[i]/pr[j])]-g1[get_id(pr[j-1])]+mod)%mod+mod)%mod;
            g2[i]=(g2[i]-1ll*pr[j]*pr[j]%mod*(g2[get_id(id[i]/pr[j])]-g2[get_id(pr[j-1])]+mod)%mod+mod)%mod;
        }
    }
    for(int i=1;i<=id[0];++i) g1[i]=(g2[i]-g1[i]+mod)%mod;
    printf("%d\n",(S(n,0)+1)%mod);
    return 0;
}

方法二:递推#

考虑把 S 的状态设计和 g 类比为 S(n,j)=inf(i)[iPminp(i)>pj]

这样目标是得到 S(n,0)+1,初始 S(n,+)=g(n)(实际上就是只有质数部分,第二维取第一个大于 n 的质数编号即可)。

递推过程:

S(n,j)={S(n,j+1)pj+12>nS(n,j+1)+pj+12pj+1e+1nf(pj+1e)(S(npj+1e,j+1)S(pj+1,j+1))+f(pj+1e+1)pj+12n

这里对 pj+1e+1 做限制的原因是保证了 npj+1epj+1,从而消去 S(pj+1,j+1) 是正确的。

复杂度同上,但常数较大,优越性在于递推算法求出了块筛。

点击查看代码
inline int S1(ll N){
    N%=mod;
    return N*(N+1)%mod*inv2%mod;
}
inline int S2(ll N){
    N%=mod;
    return N*(N+1)%mod*(2*N+1)%mod*inv6%mod;
}
inline int f(ll N){
    N%=mod;
    return N*(N-1+mod)%mod;
}

ll n;
int lim;
int pr[maxn],cntpr;
bool vis[maxn];
ll id[maxn<<1];
int id1[maxn],id2[maxn];
int g1[maxn<<1],g2[maxn<<1];
int S[maxn<<1];

inline int get_id(ll N){
    if(N<=lim) return id1[N];
    else return id2[n/N];
}

inline void linear_sieve(){
    for(int i=2;i<=lim;++i){
        if(!vis[i]) pr[++cntpr]=i;
        for(int j=1;j<=cntpr&&i*pr[j]<=lim;++j){
            vis[i*pr[j]]=1;
            if(i%pr[j]==0) break;
        }
    }
}

int main(){
    scanf("%lld",&n);
    lim=sqrt(n)+1;
    linear_sieve();
    for(ll l=1,r;l<=n;l=r+1){
        r=n/(n/l);
        id[++id[0]]=n/l;
        if(n/l<=lim) id1[n/l]=id[0];
        else id2[n/(n/l)]=id[0];
        g1[id[0]]=(S1(n/l)-1+mod)%mod,g2[id[0]]=(S2(n/l)-1+mod)%mod;
    }
    for(int j=1;j<=cntpr;++j){
        for(int i=1;i<=id[0]&&1ll*pr[j]*pr[j]<=id[i];++i){
            g1[i]=(g1[i]-1ll*pr[j]*(g1[get_id(id[i]/pr[j])]-g1[get_id(pr[j-1])]+mod)%mod+mod)%mod;
            g2[i]=(g2[i]-1ll*pr[j]*pr[j]%mod*(g2[get_id(id[i]/pr[j])]-g2[get_id(pr[j-1])]+mod)%mod+mod)%mod;
        }
    }
    for(int i=1;i<=id[0];++i) S[i]=g1[i]=(g2[i]-g1[i]+mod)%mod;
    for(int j=cntpr;j>=1;--j){
        for(int i=1;i<=id[0]&&1ll*pr[j]*pr[j]<=id[i];++i){
            ll now=pr[j];
            for(int e=1;now*pr[j]<=id[i];++e,now*=pr[j]){
                S[i]=(S[i]+1ll*f(now)*(S[get_id(id[i]/now)]-S[get_id(pr[j])]+mod)%mod+f(now*pr[j]))%mod;
            }
        }
    }
    printf("%d\n",(S[1]+1)%mod);
    return 0;
}

例题#

LibreOJ-6235 区间素数个数#

只需要做 Min_25 的第一步,求 g 即可。

LibreOJ-6027 from CommonAnt 质数计数 I#

就是求模 41 的质数个数,注意到求 g 部分是把 minp(i)=pj 分成了 pj 和其他两部分,而 g(npj,j1) 中每个数的余数乘上 pj 的余数才是得到 minp(i)=pj 合数的余数。因此不能只求余数为 1 的,计算时按余数分组即可。

LibreOJ-6028 from CommonAnt 质数计数 II#

与上一题类似

LibreOJ-6053 简单的函数#

先按 f(p)=p1g,第二步算之前给前缀和再加上 2 即可。

注意第一步只需要保证完全积性,第二步只需要保证积性,所以这样处理是正确的。

LibreOJ-6785 简单的函数 10^13 版#

要求块筛,用递推方法,需要大量卡常。

LibreOJ-6181 某个套路求和题#

f(n) 不是积性函数,但是很简洁,容易发现质数位置是 1,含平方因子位置是 0

不含平方因子的合数位置,设共 k 个质因子,那么只关心 μ(d)=1 的位置,也就是 k 中选出奇数个,方案数 2k1,因此不含平方因子的合数位置是 1

只看合数位置可以拟合成 μ2(n),这样质数位置从 1 变成了 1,答案就应该是:

inμ2(i)2in[iP]

前一个式子直接 Min_25 前缀和即可,容易发现后一个式子等价于第一步的 g 函数。

参考资料#

狄利克雷卷积#

杜教筛#

PN 筛#

Min_25 筛#

作者:SoyTony

出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Dirichlet_Convolution_and_Senior_Sieve_Algorithms.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   SoyTony  阅读(357)  评论(7编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示