杜教筛入门

当学 Min 25 的一个前置知识。


算法内容。

定义 S(n)=i=1nf(i)。对于一个函数 g,有:

i=1n(fg)(i)=i=1nd|if(id)g(d)=d=1ng(d)i=1ndf(i)=d=1ng(d)S(nd)g(1)S(n)=i=1n(fg)(i)d=2ng(d)S(nd)S(n)=i=1n(fg)(i)d=2ng(d)S(nd)g(1)

所以如果存在函数 g,满足:

  1. g(1)0
  2. i=1n(fg)(i) 可以快速计算
  3. g(i) 可以快速计算

可以通过记忆化搜索+数论分块快速计算 S(n)。可以用 unordered_map存储结果。

直接计算复杂度为 O(n34)。更好的计算是预处理前 O(n23)S,可以做到 O(n23) 的复杂度。

具体证明可以参考 link。证明本质是积分,简单的。

常用构建函数 g 技巧:

  1. d|nnμ(d)=[n=1]
  2. d|nnu(d)nd=φ(d)
  3. d|nnφ(d)=n
  4. ik·(ni)k=nk

例子:


S(n)=i=1nμ(i)

g(n)=1,也即常函数。

i=1n(gμ)(i)=[n1]

S(n)=[n1]i=2nS(ni)


S(n)=i=1nφ(i)

g(n)=1

i=1n(gφ)(i)=n(n+1)2

S(n)=n(n+1)2i=2nS(ni)


实操

计算 i=1nj=1nijgcd(i,j)

先化简:

i=1nj=1nijgcd(i,j)=d=1ni=1nj=1n[gcd(i,j)=d]ijd=d=1ni=1idnj=1jdn[gcd(i,j)=1]ijd3=d=1nd3i=1idnj=1jdnt|i,t|jμ(t)ij=x=1nt=1xtnx3μ(t)i=1inxtj=1jnxtijt2=x=1nt=1xtnx3μ(t)t2(nxt(nxt+1)2)2=T=1nT2d|Tμ(d)Td(nT(nT+1)2)2=T=1nT2φ(T)(nT(nT+1)2)2

h(n)=n2(n+1)24f(n)=n2φ(n)

对于上式,后者可以数论分块,问题化为求解 f 的前缀和。

g(n)=n2

i=1n(fg)(i=i=1nd|id2φ(d)i2d2=i=1ni3=n2(n+1)24

g(1)=1

g(i) 可以快速计算。

所以 Sf(n)=n2(n+1)24i=2ni2Sf(ni)

数论分块即可。

模版代码一份。

#include<bits/stdc++.h>
using namespace std;
#define N 1050500
#define int long long 
const int it=1e6+7;
int v[N],pri[N],tot,p,phi[N],n,m,s[N],inv2,inv6,inv4;
int power(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=ans*a%p;
        a=a*a%p;b>>=1;
    }
    return ans;
}
unordered_map<int,int>sit;
void init(){
    phi[1]=1;
    for(int i=2;i<it;i++){
        if(!v[i]){
            pri[++tot]=i;phi[i]=i-1;
        }
        for(int j=1;j<=tot&&i*pri[j]<it;++j){
            v[pri[j]*i]=1;
            if(i%pri[j]==0){
                phi[pri[j]*i]=pri[j]*phi[i];break;
            }
            phi[pri[j]*i]=phi[pri[j]]*phi[i];
        }
    }
    for(int i=1;i<it;++i)s[i]=(s[i-1]+i*i%p*phi[i]%p)%p;
}
int h(int n){
    n%=p;
    return n*n%p*(n+1)%p*(n+1)%p*inv4%p;
}
int pfs(int n){
    n%=p;
    return n*(n+1)%p*(n+n+1)%p*inv6%p;    
}
int calc(int n){
    if(n<it)return s[n];
    if(sit[n]!=0)return sit[n];
    int res=h(n);int lst=1,cur=0;
    for(int l=2,r;l<=n;l=r+1){
        r=min(n,n/(n/l));cur=pfs(r);
        res=(res+p-(cur-lst)%p*calc(n/l)%p)%p;lst=cur;
    }res=(res%p+p)%p;
    return sit[n]=res;
}
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>p;cin>>n;inv2=power(2,p-2);inv4=power(4,p-2);inv6=power(6,p-2);
    init();
    int ans=0,lst=0;
    for(int l=1,r;l<=n;l=r+1){
        r=min(n,n/(n/l));int cur=calc(r);
        ans+=(cur-lst)*h(n/l)%p;ans%=p;lst=cur;
    }
    ans=(ans%p+p)%p;
    cout<<ans<<"\n";
}
posted @   spdarkle  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示