Skip to content
liuenyin's blog
Go back

浅谈莫反与筛法

Edit page

筛法

前置知识

数论函数:定义在正整数上的函数。

例:1(n)=1,idk(n)=nk,2(n)=21(n)=1,\operatorname{id_k}(n)=n^k,2(n)=2

a,b,gcd(a,b)=1f(ab)=f(a)f(b)\forall a,b, \gcd(a,b)=1\Rightarrow f(ab)=f(a)f(b) 则称 ff 积性。

迪利克雷卷积: 两个数论函数 f,gf,g 的迪利克雷卷积为 (fg)(n)=inf(i)g(ni)(f*g)(n)=\sum \limits_{i|n}f(i) g(\frac ni) 。满足交换律,结合律。若 f,gf,g 积性,则 fgf*g 积性(若完全积性,则没有传递性)。

ϵ(n)=[n=1]ϵf=f11=σ0=in11idk=:σkφ1=id\epsilon (n)=[n=1]\\\epsilon *f=f\\1*1=\sigma_0=\sum \limits_{i|n} 1\\1*\operatorname{id}_k=: \sigma _k \\ \varphi*1=\operatorname{id}

迪利克雷逆:若 fg=ϵf*g=\epsilon,则 ggff 的迪利克雷逆,记 ggf1f^{-1}

[n=1]=inf1(i)f(ni)=f(1)f1(n)+i>1,inf(i)f1(ni)f1(n)=[n=1]i>1,inf(i)f1(ni)f(1)[n=1]=\sum\limits_{i|n} f^{-1}(i)f(\frac ni)=f(1)f^{-1}(n)+\sum\limits_{i>1,i|n}f(i)f^{-1}(\frac ni)\\f^{-1}(n)=\dfrac{[n=1]-\sum \limits_{i>1,i|n} f(i)f^{-1}(\frac ni)}{f(1)}

于是 ff 有迪利克雷逆的充要条件是 f(1)0f(1)≠0

整除

[[ni]j]=[nij],in,[n[ni]][\frac{[\frac ni]}{j}]=[\frac n{ij}],\forall i\le n,[\frac{n}{[\frac ni]}] 是最大使 [nx]=[ni][\frac{n}x]=[\frac ni]xx

Dn={[ni]i=1,2,...,n},Dn=O(n)D_n=\{[\frac ni]|i=1,2,...,n\}, |D_n|=O(\sqrt n) 。因此会有整除分块这种优化方式。

μ(n)\mu(n)

莫比乌斯函数 μ\mu 定义为 111^{-1}

nn 含有大于 11 的平方因子, μ(n)=0\mu(n)=0 。否则,令 kknn 含有的不同质因子个数,μ(n)=(1)k\mu(n)=(-1)^k

由定义,

f(n)=ing(i)g(n)=inμ(ni)f(i)f(n)=nig(i)g(n)=niμ(in)f(i)f(n)=\sum \limits_{i|n}g(i)\Leftrightarrow g(n)=\sum \limits_{i|n}\mu(\frac ni) f(i)\\f(n)=\sum \limits_{n|i}g(i)\Leftrightarrow g(n)=\sum \limits_{n|i}\mu(\frac in) f(i) 。(莫比乌斯反演公式)

线性筛可以 O(n)O(n) 求解积性函数。

例1

i=1nj=1m[σ1(gcd(i,j))a]σ1(gcd(i,j)),n,m105,q2×104\sum \limits_{i=1}^{n } \sum \limits_{j=1}^{m} [\sigma_1(\gcd(i,j)) \le a] \sigma_1(\gcd(i,j)),n,m\le 10^5,q\le 2\times 10^4

i=1nj=1m[σ1(gcd(i,j))a]σ1(gcd(i,j))=d=1ni=1ndj=1md[σ1(d)a][gcd(i,j)=1]σ1(d)=d=1nσ1(d)[σ1(d)a]i=1ndj=1md[gcd(i,j)=1]=d=1nσ1(d)[σ1(d)a]i=1ndj=1mdkgcd(i,j)μ(k)=d=1nσ1(d)[σ1(d)a]k=1ndndkmdkμ(k)\begin{align} &\sum \limits_{i=1}^{n } \sum \limits_{j=1}^{m} [\sigma_1(\gcd(i,j)) \le a] \sigma_1(\gcd(i,j))\\ &=\sum \limits_{d=1}^n \sum \limits_{i=1}^{\frac nd} \sum \limits_{j=1}^{\frac md}[\sigma_1(d)\le a][\gcd(i,j)=1] \sigma_1(d) \\&= \sum \limits_{d=1}^n \sigma_1(d)[\sigma_1(d)\le a]\sum \limits_{i=1}^\frac nd\sum\limits_{j=1}^\frac md [\gcd(i,j)=1] \\&= \sum \limits_{d=1}^n \sigma_1(d)[\sigma_1(d)\le a]\sum \limits_{i=1}^\frac nd\sum\limits_{j=1}^\frac md \sum \limits_{k|\gcd(i,j)}\mu(k) \\&= \sum \limits_{d=1}^n \sigma_1(d)[\sigma_1(d)\le a]\sum \limits_{k=1}^\frac nd\lfloor \frac{n}{dk}\rfloor \lfloor \frac{m}{dk} \rfloor \mu(k) \end{align}

T=dkT=dk

=T=1nnTmTkTμ(k)σ1(Tk)[σ1(Tk)a]\begin{align}&= \sum \limits_{T=1}^n \lfloor \frac{n}{T}\rfloor \lfloor \frac{m}{T} \rfloor \sum \limits_{k|T} \mu(k) \sigma_1(\frac Tk) [\sigma_1(\frac Tk)\le a] \end{align}

后面那个式子可以看为一个关于 (T,a)(T,a) 的函数。考虑预处理 σ1(x)\sigma_1(x) 并将函数值从小到大排序,将询问按照 aa 从小到大排序。每次前面的整除分块相当于求一个前缀和询问,我们需要用数据结构维护后面那个东西。树状数组可以。每次 aa 变大后,加入一个 σ1(k)\sigma_1(k)相当于在 k,2k,3k,...k,2k,3k,... 增加 μ(1)σ1(k),μ(2)σ1(k)...\mu(1)\sigma_1(k), \mu(2)\sigma_1(k)...。最终插入次数调和级数。于是这部分复杂度 O(nlog2n)O(n \log^2 n)。整除分块复杂度 O(Tnlogn)O(T \sqrt n \log n)。 ::::info[代码]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
const int N=1e5+5,M=(1ll<<31);
struct BIT{
    int t[N<<1];
    int query(int x){
        int ret=0;
        for(;x;x-=(x&-x)){
            ret+=t[x];ret%=M;
        }
        return ret;
    }
    void add(int pos,int x){
        for(;pos<N;pos+=(pos&-pos))t[pos]+=x,t[pos]+=M,t[pos]%=M;
    }
}t;
pair<int,int> s1[N];
int mu[N],p[N],pcnt;
bool isp[N];
void init(){
    mu[1]=1;
    for(int i=2;i<=N-5;i++){
        if(!isp[i])p[++pcnt]=i,mu[i]=-1;
        for(int j=1;j<=pcnt and (i*p[j])<=N-5;j++){
            isp[i*p[j]]=1;
            if(i%p[j]==0){
                mu[i*p[j]]=0;
                break;
            }
            mu[i*p[j]]=-mu[i];
        }
    }
    for(int i=1;i<=N-5;i++){
        s1[i].second=i;
        for(int j=i;j<=N-5;j+=i){
            s1[j].first+=i;
        }
    }
    // cerr<<s1[1].first<<" "<<s1[2].first<<" "<<s1[3].first<<" "<<s1[4].first<<endl;
    sort(s1+1,s1+1+100000);
}

struct query{
    int n,m,a,id;
    bool operator<(const query &A){return a<A.a;}
}q[N];
int ans[N];
int T;
signed main(){
    cin>>T;
    init();
    for(int i=1;i<=T;i++){
        cin>>q[i].n>>q[i].m>>q[i].a;q[i].id=i;
        if(q[i].n>q[i].m)swap(q[i].n,q[i].m);
    }
    sort(q+1,q+1+T);int itr=1;
    for(int i=1;i<=T;i++){
        while(itr<=1e5 and s1[itr].first<=q[i].a){
            for(int j=s1[itr].second;j<=N-5;j+=s1[itr].second){
                t.add(j,s1[itr].first*mu[j/s1[itr].second]);
            }
            itr++;
        }
        for(int l=1,r;l<=q[i].n;l=r+1){
            r=min(q[i].n/(q[i].n/l),q[i].m/(q[i].m/l));
            ans[q[i].id]+=((q[i].n/l)*(q[i].m/l)%M)*(t.query(r)-t.query(l-1))%M;
            ans[q[i].id]%=M;
        }
    }
    for(int i=1;i<=T;i++)cout<<(ans[i]+M)%M<<endl;
    return 0;
}

::::

杜教筛

在一些问题中,我们想对于一个特定的 nn 求一个积性函数的前缀和 i=1nf(i)\sum \limits_{i=1}^n f(i) 。我们想将这个与整除分块相结合。例如,要快速的求 i=1nj=1m[gcd(i,j)=1]\sum \limits_{i=1}^n \sum \limits_{j=1}^m [\gcd(i,j)=1],我们需要知道所有 DnDmD_n \cup D_mμ\mu 的前缀和。

考虑迪利克雷卷积,设 h=fgh=f*ghh 的前缀和为

i=1nh(i)=i=1njif(j)g(ji)=i=1nj=1[ni]f(i)g(j)=i=1nf(i)G([ni])\sum \limits_{i=1}^n h(i)=\sum \limits_{i=1}^n \sum \limits_{j|i} f(j)g(\frac ji)=\sum \limits_{i=1}^n\sum \limits_{j=1}^{[\frac ni]} f(i)g(j)\\=\sum \limits_{i=1}^nf(i)G([\frac ni])

于是 G(i)=i=1nh(i)i=2nf(i)G([ni])G(i)=\sum \limits_{i=1}^n h(i)-\sum \limits_{i=2}^n f(i)G([\frac ni])

通过这个式子可以递归计算 gg 的前缀和(在 nn 的整除点处),要求能快速计算 f,hf,h 的前缀和。

进行优化后(线性筛计算前 n23n^{\frac 23} 个函数值,然后杜教筛剩下的)可以做到 O(n23)O(n^{\frac 23})

Powerful Number\text{Powerful Number}

杜教筛求解 ff 的前缀和需要 g,h,fg1=h\exist g,h, f*g^{-1}=hg,hg,h 前缀和易求,对积性函数要求较高,部分函数可能难以使用杜教筛,这时候可以使用PN筛。

n=p1k1p2k2pmkmn=p_1^{k_1} p_2^{k_2}\cdots p_m^{k_m},则 nn 是一个 powerful number\text{powerful number} 当且仅当 i,ki2\forall i,k_i\ge 2。所有 powerful number\text{powerful number} 都可以表示成 a2b3a^2 b^3 的形式。

nn 以内的 powerful number\text{powerful number} 个数是 O(n)O(\sqrt n) 的。

假设我们想要求解 ff 的前缀和,但 ff 不满足杜教筛的条件,此时我们想要找满足杜教筛条件的 gg,并且对于任意质数 pp 满足 f(p)=g(p)f(p)=g(p),则我们有 h=fg1h=f*g^{-1}h(1)g(p)+h(p)g(1)=g(p)+h(p)=f(p)+h(p)=f(p)h(1)g(p)+h(p)g(1)=g(p)+h(p)=f(p)+h(p)=f(p)h(p)=0h(p)=0,所以仅当 nnpowerful number\text{powerful number}h(n)h(n) 才有可能非 00

f=hg,ff=h*g,f 的前缀和可以表示为:

i=1nf(i)=i=1nj=1[ni]h(i)g(j)=i=1nh(i)j=1[ni]g(j)\sum \limits_{i=1}^n f(i)=\sum \limits_{i=1}^n \sum \limits_{j=1}^{[\frac{n}{i}]} h(i)g(j)=\sum \limits_{i=1}^nh(i) \sum \limits_{j=1}^{[\frac ni]}g(j),可以杜教筛 gg,在每个 powerful number\text{powerful number} 处求解 hh,即可优化复杂度。考虑计算 h(pc)h(p^c),一种方式是 f=ghh(pc)=f(pc)i=1cg(pi)h(pci)f=g*h\Rightarrow h(p^c)=f(p^c)-\sum \limits_{i=1}^c g(p^i)h(p^{c-i}),然后进行枚举(根据 O(n)O(\sqrt n) 内素数个数为 O(nlnn)O(\frac{n}{\ln n}),每个素数的指数至多为 lnn\ln n,于是这一部分时间复杂度为 O(nlnn)O(\sqrt n\ln n))最后搜索 nn 以内的 powerful number\text{powerful number} 即可。

例:LOJ #6053. 简单的函数

积性函数 f(n)f(n) 满足 f(pc)=pxorcf(p^c)=p \operatorname{xor} c,求 ff 的前缀和。n1010n \le 10^{10}

f(p)={p+1(p=2)p1otherwisef(p)=\begin{cases}p+1& (p=2)\\p-1 &\text{otherwise} \end{cases},故可以构造 g(n)={3φ(n)2nφ(n)otherwiseg(n)=\begin{cases}3\varphi(n) & 2|n \\\varphi(n) &\text{otherwise} \end{cases}

gg 为积性函数。

G(n)=i=1n[imod2=1]φ(i)+3i=1n[imod2=0]φ(i)=i=1nφ(i)+2i=1n2φ(2i)\begin{align} G(n)&=\sum \limits_{i=1}^n [i\bmod 2=1]\varphi(i) +3\sum \limits_{i=1}^n [i\bmod2=0]\varphi(i) \\ &=\sum \limits_{i=1}^n \varphi(i) +2\sum \limits_{i=1}^\frac n2 \varphi(2i) \end{align}

S1(n)=i=1nφ(i),S2(n)=i=1nφ(2i)S_1(n)=\sum\limits_{i=1}^n \varphi(i),S_2(n)=\sum \limits_{i=1}^n \varphi(2i)G(n)=S1(n)+2S2(n2)G(n)=S_1(n)+2S_2(\frac n2)

2n2|n 时,

S2(n)=i=1nφ(2i)=i=1n2(φ(2(2i1)))+φ(2(2i))=i=1n2φ(2i1)+2φ(2i)  [1]=S1(n)+S2(n2)\begin{align}S_2(n)&= \sum \limits_{i=1}^n \varphi(2i)\\&= \sum \limits_{i=1}^\frac n2 (\varphi(2(2i-1)))+\varphi(2(2i)) \\ &=\sum \limits_{i=1}^{\frac n2} \varphi(2i-1)+2\varphi(2i) \ \ ^{[1]} \\ &=S_1(n)+S_2(\frac n2)\end{align}

否则, φ(2n)=φ(n)\varphi(2n)=\varphi (n)S2(n)=S1(n1)+S2(n2)+φ(n)=S1(n)+S2([n2])S_2(n)=S_1(n-1)+S_2(\frac n2)+\varphi(n)=S_1(n)+S_2([\frac n2])

S1S_1 可以杜教筛。于是我们可以快速求 gg 的前缀和。

然后用 h(pc)=f(pc)i=1cg(pi)h(pci)h(p^c)=f(p^c)-\sum \limits_{i=1}^c g(p^i)h(p^{c-i}) 可以计算 hh 的前缀和。

i=1nf(i)=i=1nj=1[ni]h(i)g(j)=i=1nh(i)j=1[ni]g(j)\sum \limits_{i=1}^n f(i)=\sum \limits_{i=1}^n \sum \limits_{j=1}^{[\frac{n}{i}]} h(i)g(j)=\sum \limits_{i=1}^nh(i) \sum \limits_{j=1}^{[\frac ni]}g(j)

[1]:[1]: 根据 φ(n)=ni=1kpk1pk\varphi(n)= n\prod\limits_{i=1}^k \frac{p_k-1}{p_k} ( pkp_knn 的不同质因数) 推导可得。

代码

Min25筛

Min25筛可以在 O(n34logn)O(\frac{n^{\frac{3}{4}}}{\log n}) 的复杂度内求一个积性函数 f(p)f(p) 的前缀和,要求 f(p)f(p) 为关于 pp 的简单多项式,且 f(pc)f(p^c) 可以快速计算。

我们对这个 f(p)f(p) 的前缀和每一项按照素数和合数(和1)进行分类:

1inf(i)=pprime,pnf(p)+iprime,inf(i)\sum\limits_{1\le i\le n}f(i)=\sum \limits_{p\in prime,p\le n}f(p)+\sum \limits_{i \notin prime,i\le n}f(i)

枚举每个合数的最小质因子及次数:

pprime,pnf(p)+1pk,1pnf(pk)1i[npk],lp(i)>pf(i)\sum \limits_{p\in prime,p\le n}f(p)+\sum \limits_{1\le p^k,1\le p\le \sqrt n}f(p^k) \sum \limits_{1\le i\le [\frac{n}{p^k}],\operatorname{lp}(i)>p}f(i)

其中 lp(x)\operatorname{lp}(x) 代表 xx 的最小质因子。

我们分成两步算上面这个式子。

1.求 pprime,pnf(p)\sum \limits_{p\in prime,p\le n}f(p)

g(n,j)=(iprime or lp(i)>pj)and inikg(n,j)=\sum \limits_{(i\in prime \ or \ \operatorname{lp}(i)>p_j) and \ i\le n} i^k

iki^k 为上文中“简单多项式”的一项(相当于把函数拆开来)。

pj2>np_j^2>n 时,最小的满足 lp(x)=pj\operatorname{lp}(x)=p_j 的数为 pj2>np_j^2>n,不会有贡献。

否则,考虑从 g(n,j1)g(n,j-1) 转移到 g(n,j)g(n,j)。这个转移的过程中剔除了最小质因子为 pjp_j 且不满足条件的数的贡献,即:

g(n,j)=g(n,j1)pjk[g(npj,j1)g(pj1,j1)]g(n,j)=g(n,j-1)-p_j^k[g(\lfloor\frac{n}{p_j}\rfloor,j-1)-g(p_{j-1},j-1)]

关于 pjk[g(npj,j1)g(pj1,j1)]p_j^k[g(\lfloor\frac{n}{p_j}\rfloor,j-1)-g(p_{j-1},j-1)]

这是最小质因子恰为 pjp_j 的合数的贡献。这些合数可以表示为 pj×xp_j\times x,其中 xx 满足 lp(x)pj\operatorname{lp}(x)\ge p_j。如何计算 xk\sum x^k

所以我们有了 gg 的递推式:

g(n,j)={g(n,j1)pj2>ng(n,j1)pjk[g([npj],j1)g(pj1,j1)]pj2ng(n,j)=\left\{\begin{array}{l}g(n,j-1)& p_j^2>n\\g(n,j-1)-p_j^k[g([\frac{n}{p_j}],j-1)-g(p_j-1,j-1)] & p_j^2\leq n \end{array}\right.

g(pj1,j1)g(p_j-1,j-1) 就是 i=1j1pik\sum \limits_{i=1}^{j-1}p_i^kjO(nlnn)j\le O(\frac{\sqrt n}{\ln n}),可以直接线性筛。

同时根据整除点的性质(Dn=O(n)|D_n|=O(\sqrt n)),只需处理所有 [npj][\frac{n}{p_j}] 位置的 gg

在这一步之后,将 g(n,j)g(n,j) 线性组合为 (iprime or lp(i)>pj)and inf(i)\sum \limits_{(i\in prime \ or \ \operatorname{lp}(i)>p_j) and \ i\le n} f(i)

2.求后面一部分

S(n,j)=2in,lp(i)>pjf(i)S(n,j)=\sum \limits_{2\le i\le n,\operatorname{lp}(i)>p_j}f(i)

则我们的答案就是 S(n,0)S(n,0)

可以将上式中满足条件的数分为两个部分:

所以有

S(n,j)=g(n,x)g(pj1,j1)+pke,k>jf(pke)(S([npke],k)+[e1])S(n,j)=g(n,x)-g(p_j-1,j-1)+\sum_{p_k^e,k>j}f(p_k^e)(S([\frac{n}{p_k^e}],k)+[e\neq 1])

考虑直接爆搜。另外注意到上式不包含 f(1)f(1),直接加上即可。

例题:

SP34096 DIVCNTK

i=1nσ0(ik)\sum \limits_{i=1}^n \sigma_0(i^k)

不难发现 f(1)=1,f(p)=k+1,f(pe)=ke+1,f(ab)=f(a)f(b)f(1)=1,f(p)=k+1,f(p^e)=ke+1,f(ab)=f(a)f(b)gcd(a,b)=1\gcd(a,b)=1)。

直接做即可。

::::info[代码]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int unsigned long long//对 2^64取模
const int N=1e7+5;
int p[N],pcnt;
bool isp[N];
void init(int n){
    // p[0]=1;
    for(int i=2;i<=n;i++){
        if(!isp[i]){
            p[++pcnt]=i;
            // cout<<i<<endl;
        }
        for(int j=1;j<=pcnt and i*p[j]<=n;j++){
            isp[i*p[j]]=1;
            if(i%p[j]==0)break;
        }
    }
}
int T,n,k,sqn;
int g[N];//g[i] g(第i个整除点,j)的值(其中j被压维)
int d[N],ptr;//整除点
int pos[3][N];//d[x]的索引位置,pos[1][x]是小的(n/i)的位置
//,pos[2][x]是大的(n/i)的位置(存的是 x=n/(n/i))
int S(int x,int y){
    //S(n,j)
    if(p[y]>x)return 0;
    int posx;
    if(x<=sqn)posx=pos[1][x];
    else posx=pos[2][n/x];
    int ans=g[posx]-y*(k+1);//y*(k+1)=g(p_y-1,y-1)
    for(int i=y+1,tmp;i<=pcnt and p[i]*p[i]<=x;i++){
        tmp=p[i];
        for(int e=1;tmp<=x;e++,tmp*=p[i]){
            //枚举p_i^e
            ans+=(k*e+1)*S(x/tmp,i);
            if(e>1)ans+=k*e+1;
        }
    }
    return ans;
}
signed main(){
    cin>>T;
    while(T--){
        pcnt=0;
        cin>>n>>k;
        sqn=sqrt(n);
        init(sqn);ptr=0;
        for(int i=1;i<=n;i=n/(n/i)+1){
            d[++ptr]=n/i;
            g[ptr]=n/i-1;//g(n/i,0)=n/i-1(不包含f(1))
            if(n/i<=sqn)pos[1][n/i]=ptr;
            else pos[2][n/(n/i)]=ptr;
        }
        for(int i=1;i<=pcnt;i++){
            // cerr<<pcnt<<" "<<ptr<<" "<<p[i]<<" "<<d[1]<<endl;
            for(int j=1;j<=ptr and p[i]*p[i]<=d[j];j++){
                //计算g(d[j],i)
                int itr;
                if(d[j]/p[i]<=sqn)itr=pos[1][d[j]/p[i]];
                else itr=pos[2][n/(d[j]/p[i])];
                // cout<<i<<" "<<j<<" "<<itr<<endl;
                g[j]-=g[itr]-(i-1);//f(p)=k+1,多项式和p无关(相当于0次项),不用乘p的幂            
            }
        }
        for(int i=1;i<=ptr;i++)g[i]*=(k+1);//f(p)=k+1,现在 g[x]=g(第x个整除点,n)=【<=第x个整除点的f(p)和】
        // cerr<<g[1]<<" "<<g[2]<<" "<<g[3]<<" "<<g[4]<<endl;
        cout<<S(n,0)+1<<endl;
    }
    return 0;
}

::::

LOJ #6202 叶氏筛法

f(p)=pf(p)=p。只需要第一部分。

LOJ #572 Misaka Network 与求和


Edit page
Share this post on:

Previous Post
THUPC 2026(初赛) 游记
Next Post
构造及类构造例题