题解 P2158 【[SDOI2008]仪仗队】

· · 题解

题目

update on 2023.09.06

加入了一个适用性比较广的筛法,在文章末。

本文进行了一定的更新

  1. 优化了 Markdown 中 Latex 语句的运用,加强了可读性
  2. 补充了“我们仍不曾知晓得 消失的 性质5 ”,加强了推导的严谨性
  3. 介于使用了新的推导方法,调整了推导顺序
  4. 补充了关于线性筛的欧拉函数性质8
  5. 又又又又又 修改了部分错误(工程量太大,老是出错)
  6. 安利了 3b1b 的链接,虽然与本文章无关,但对我们的思维提升很有利
  7. 将欧拉函数的定义正确修改
  8. 减少了 ans(n) 推导公式的争议性,加强了推导过程的严谨性
  9. 更新了 python 代码,方便跟大家学习装逼

【分析】

我们设 左数第 a 列,后数第 b 列的同学,坐标为 (a-1,b-1)

那么,C君的坐标为 (0,0)

现在我们即对题目完成建模:在一个从 (0,0)(n-1,n-1) 的点阵中,有多少点能被 (0,0) 点“看到” ?

根据图像,易得出,所有的点都是关于 y=x 对称的(这个性质我们待会儿要用到)。

根据题目,如何判定一个点是否看得到呢?

我们假设对于一个存在的点 (x,y),那么,根据图像的直观意义,便可以知道,原点和该点连线方向往后的每一个点,都是看不到的。

即对任意整点 (x,y),点 (\lambda x,\lambda y),(\lambda>1) 是看不到的。

我们可以显然发现:gcd(\lambda x,\lambda y)=\lambda gcd(x,y)>\lambda\cdot1=\lambda>1

也就是说,对于一个点 (x,y) ,当 gcd(x,y) \neq 1 时这个点看不到。

而对于任意满足 gcd(x,y)=1 的点 (x,y) ,若它会被遮挡,则必定有整点 ({x\over m},{y\over m}),(m>1) 存在。

根据 gcd(x,y)=1 ,我们能发现这样的点不存在

所以我们得出结论:一个点 (x,y) 能被看到,当且仅当 gcd(x,y)=1

因此,题目对于给定的 n 所求的点数 ans(n) 即满足

ans(1)=0 \displaystyle ans(n)= \sum_{x=0}^{n-1} \sum_{y=0}^{n-1} [gcd(x,y)=1],n \geq 2

当然,我们知道 gcd(0,i)=gcd(i,0)=i

显然,这个式子可以展开为

\displaystyle ans(n)=\sum_{x=0}^{n-1} \sum_{y=0}^{x-1}[gcd(x,y)=1]+ \sum_{x=0}^{n-1} \sum_{y=x}^x [gcd(x,y)=1] + \sum_{x=0}^{n-1} \sum_{y=x+1}^{n-1}[gcd(x,y)=1],n \geq 2

又因为 y=x 上能被看到的点只有点 (1,1) ,所以我们可以直接考虑这个点,然后忽略这条直线了

因此,我们可以将这个式子化简为

\displaystyle ans(n)=\sum_{x=0}^{n-1} \sum_{y=0}^{x-1}[gcd(x,y)=1]+ 1 + \sum_{x=0}^{n-1} \sum_{y=x+1}^{n-1}[gcd(x,y)=1],n \geq 2

由文章最上方写的图像的原理,我们可以知道

\displaystyle \sum_{x=0}^{n-1} \sum_{y=0}^{x-1}[gcd(x,y)=1]= \sum_{x=0}^{n-1} \sum_{y=x+1}^{n-1}[gcd(x,y)=1]

因此 \displaystyle ans(n)=2\sum_{x=0}^{n-1} \sum_{y=0}^{x-1}[gcd(x,y)=1]+1,n \geq 2

又因为根据欧拉函数 \boldsymbol \varphi (n) 的定义,为小于等于 n 的自然数中,与 n 互质的数的个数

ab 互质,当且仅当 gcd(a,b)=1 。因此得出 1 与所有数互质

\displaystyle \boldsymbol \varphi (n)=\sum_{i=0}^{n-1} [gcd(i,n)=1]

因此,我们可以将式子简化为:

\displaystyle ans(n)=2\sum_{x=0}^{n-1} \boldsymbol \varphi(x) +1,n \geq 2

个人习惯这样表示:\displaystyle \Phi(n)=\sum_{i=0}^n\boldsymbol\varphi(i)

所以,式子我们也可以这么写:

\displaystyle ans(n)=2\Phi(n-1) +1,n \geq 2

终于,我们得到了本题的答案:

ans(1)=0 \\\\ \displaystyle ans(n)=2\Phi(n-1) +1,n \geq 2 \end{cases}

那么,这里对于如何求 \boldsymbol \varphi(n) 做一个讲解,已经掌握的大佬们请直接跳过。

首先,\boldsymbol \varphi(n) 的定义是在小于 n 的正整数中,与 n 互质的数的个数。

因此,我们先考虑一下,假设存在一个质数 p

那么, p 的因数只有 1p

在小于 p 的正整数中,不存在任何数含有因数 p

因此, p 和任意比它小的正整数都互质。

也就是性质1 \boldsymbol \varphi(p)=p-1 ( p-1 个数比它小)

接下来,我们考虑 p^k,k \in Z_+k>1

p^k$ 有且仅有质因数 $p

且比它小的 (p^k-1) 个数中,存在一下几个数也含有质因数 p

p\ ,\ 2p\ ,\ 3p\ ,\ 4p\ \dots\ (p^{k-1}-1)p

(p^{k-1}-1) 个数

因此,剩余的 (p^k-1)-(p^{k-1}-1)=(p^k-p^{k-1}) 个数都与之互质

于是我们可以把 k=1 看成这个的一个特例

于是我们得到性质2 \boldsymbol \varphi(p^k)=p^k-p^{k-1},k \in Z_+

同时,又因为 \boldsymbol \varphi(p^{k+1})=p^{k+1}-p^k=p\times(p^k-p^{k-1})

所以,还能得到性质3 \boldsymbol \varphi(p^{k+1})=p \times \boldsymbol \varphi(p^k)

接下来,我们考虑 \boldsymbol \varphi(p_1^{k_1} p_2^{k_2})

(p_1 \neq p_2$ 且 $p_1,p_2$ 为质数 $,k_1,k_2 \in Z_+)

那么,在比它小的 (p_1^{k_1} p_2^{k_2}-1) 个数中

p_1\ ,\ 2p_1\ ,\ 3p_1\ ,\ 4p_1\ \dots\ (p_1^{k_1-1} p_2^{k_2}-1)p_1$ 共 $(p_1^{k_1-1} p_2^{k_2}-1)$ 个数与它的最大公因数中含 $p_1

同理,共 (p_1^{k_1} p_2^{k_2-1}-1) 个数与它最大公因数含 p_2

但是,这里还有这重复枚举

因此,根据容斥原理,我们可以得到 $\boldsymbol \varphi(p_1^{k_1}p_2^{k_2}) =(p_1^{k_1} p_2^{k_2}-1)-(p_1^{k_1-1} p_2^{k_2}-1)-(p_1^{k_1} p_2^{k_2-1}-1)+(p_1^{k_1-1} p_2^{k_2-1}-1) =p_1^{k_1} p_2^{k_2}-p_1^{k_1-1} p_2^{k_2}-p_1^{k_1} p_2^{k_2-1}+p_1^{k_1-1} p_2^{k_2-1} =p_1^{k_1}p_2^{k_2}(1-{1\over p_1}-{1\over p_2}+{1\over p_1p_2}) =p_1^{k_1}p_2^{k_2}(1-{1\over p_1})(1-{1\over p_2}) =(p_1^{k_1}-p_1^{k_1-1})(p_2^{k_2}-p_2^{k_2-1})

又因为根据性质2,有

\boldsymbol \varphi(p_1^{k_1})=(p_1^{k_1}-p_1^{k_1-1}),\boldsymbol \varphi(p_2^{k_2})=(p_2^{k_2}-p_2^{k_2-1})

因此,带入上式得到性质4 \boldsymbol \varphi(p_1^{k_1}p_2^{k_2})=\boldsymbol \varphi(p_1^{k_1})\boldsymbol \varphi(p_2^{k_2})

性质5 的证明略长(因为是自己想的,所以可能有些繁琐),我放到代码下面,感兴趣的朋友们可以自行查看,我这边直接给出证明结果:

\displaystyle n=\prod_{i=1}^mp_i^{k_i}

\displaystyle \boldsymbol \varphi(n)=n\prod_{i=1}^m(1-{1\over p_i})

其中,性质5 的变式 \displaystyle \boldsymbol \varphi(n)=n\prod_{i=1}^m({p_i-1\over p_i}) 常用来求单个自变量的欧拉函数值

我们代入 \displaystyle n=\prod_{i=1}^mp_i^{k_i}

得到 \displaystyle \boldsymbol \varphi(n)=n\prod_{i=1}^m(1-{1\over p_i})=\prod_{i=1}^mp_i^{k_i}\times \prod_{i=1}^m(1-{1\over p_i})=\prod_{i=1}^m[p_i^{k_i}(1-{1\over p_i})]

\therefore \displaystyle \boldsymbol \varphi(n)=\prod_{i=1}^m(p_i^{k_i}-p_i^{k_i-1})

根据 \boldsymbol \varphi(p_i^{k_i}) 的定义,我们可以知道性质6 \displaystyle \boldsymbol \varphi(n)=\prod_{i=1}^m\boldsymbol \varphi(p_i^{k_i})

代入性质一可化简得 \displaystyle \boldsymbol \varphi(n)=\prod_{i=1}^m(p_i^{k_i}-p_i^{k_i-1})

我们再令 \displaystyle q=\prod_{i=1}^a p_i^{k_i},r=\prod_{i=a+1}^m p_i^{k_i},显然 gcd(q,r)=1

\displaystyle \boldsymbol \varphi(q)=\prod_{i=1}^a\boldsymbol \varphi(p_i^{k_i}),\boldsymbol \varphi(r)=\prod_{i=a+1}^m\boldsymbol \varphi(p_i^{k_i}),qr=\prod_{i=1}^a p_i^{k_i}\times\prod_{i=a+1}^m p_i^{k_i}=\prod_{i=1}^m p_i^{k_i}=n \therefore \displaystyle \boldsymbol \varphi(qr)=\boldsymbol \varphi(n)=\prod_{i=1}^m\boldsymbol \varphi(p_i^{k_i})=\prod_{i=1}^a\boldsymbol \varphi(p_i^{k_i})\times \prod_{i=a+1}^m\boldsymbol \varphi(p_i^{k_i})=\boldsymbol \varphi(q)\boldsymbol \varphi(r)

这就是欧拉函数的性质7 \forall gcd(a,b)=1\Leftrightarrow\boldsymbol\varphi(ab)=\boldsymbol\varphi(a)\boldsymbol\varphi(b)

因为这个特点,我们称呼欧拉函数为积性函数,即满足 \forall gcd(a,b)=1\Leftrightarrow \boldsymbol f(ab)=\boldsymbol f(a)\boldsymbol f(b) 的函数

这里补充一个 完全积性函数\forall a,b\in R\Leftrightarrow \boldsymbol f(ab)=\boldsymbol f(a)\boldsymbol f(b) ,例如函数 \boldsymbol {id}(x)=x

到此,对于本蒟蒻所知的 \boldsymbol \varphi(n) 函数性质已经全部讲完 (愣是码了不知道多少天......)

那么,我们开始讲线性筛

线性筛用到了 性质3性质7 的结论 (意思不需要懂证明)

根据 性质7 \boldsymbol \varphi(i\times prime_j)=\boldsymbol \varphi(i)\times \boldsymbol \varphi(prime_j)(prime_j\nmid i)

而满足 prime_j\mid i 时,令 ki 中不含 prime_j 的最大因数,且令 i=k\times prime_j^t

\therefore \boldsymbol \varphi(i\times prime_j)=\boldsymbol\varphi(k\times prime_j^t\times prime_j)=\boldsymbol \varphi(k)\boldsymbol \varphi(prime_j^{t+1}) \therefore \boldsymbol \varphi(i\times prime_j)=\boldsymbol \varphi(k)\times prime_j\times \boldsymbol \varphi(prime_j^t)=prime_j\times \boldsymbol \varphi(k\times prime_j^t)

根据前文定义得 \boldsymbol \varphi(i\times prime_j)=prime_j\times \boldsymbol\varphi(i)

再代入性质1,并归纳起来说,就是 \boldsymbol \varphi(i\times prime_j)=\begin{cases}\boldsymbol \varphi(i)\times (prime_j-1),prime_j\nmid i\\\ \\\boldsymbol \varphi(i)\times prime_j,\qquad\ \ prime_j\mid i\end{cases}

其实这是性质8

参考素数的线性筛,为了保证时间的线性,那么,就必须保证每个数只筛到一次

为了保证每个数只被筛到一次,一定要保证其只被其最小素因数筛到过

那么,线性筛 \boldsymbol \varphi(n) 也是一样

对于枚举到的 i(i \geq 2),我们用 pf_i 储存它的最小质因数

倘若出现 pf_i=0,则说明 i 的最小质因数在 2~(i-1) 都并未出现

因此,足以证明 i 为质数

所以,pf_i=i,\boldsymbol \varphi(i)=i-1,并将 i 存入到质数表中

接下来,无论 i 是否为质数,直接在质数表中枚举质数 prime_j

倘若 prime_j \leq pf_i 那么,显然 pf_{i \times prime_j}=prime_j

prime_j|i 那么就说明 i 含有质因数 prime_j

因此,\boldsymbol \varphi(i \times prime_j)=prime_j \times \boldsymbol \varphi(i)

否则,说明不含

\boldsymbol \varphi(i \times prime_j)=\boldsymbol \varphi(prime_j) \times \boldsymbol \varphi(i)

倘若 prime_j > pf_i 那么,数 i\times prime_j 将被数 {i \times prime_j \over pf_i} 筛到,这里可以直接开始跳出

最后,注意质数表的大小和 i \times prime_j 的大小

我就是这个地方被卡了好多次......

【代码】

那本蒟蒻就放 我码风极丑的 代码了

新代码:

#include<cstdio>
using namespace std;
#define f(a,b,c,d) for(int a=b,c=d;a<=c;a++)
int ans(int n){
    if(n==1) return 0;
    int prime[40010]={0},cnt=0,fc[40010]={0},phi[40010]={0};
    //这里 phi[n] 表示欧拉函数的前 n 项前缀和
    phi[1]=1;
    f(i,2,I,n-1){
        if(fc[i]==0){
            prime[++cnt]=i;
            fc[i]=i;
            phi[i]=i-1;
        }
        f(j,1,J,cnt)
            if(prime[j]*i>n||prime[j]>fc[i]) break;
            else{
                fc[prime[j]*i]=prime[j];
                if(prime[j]<fc[i]) phi[prime[j]*i]=(prime[j]-1)*phi[i];
                else phi[prime[j]*i]=prime[j]*phi[i];
            }
        phi[i]+=phi[i-1];
    }
    return (phi[n-1]<<1|1);
}
int main(){
    int n;
    scanf("%d",&n);
    printf("%d", ans(n) );
    return 0;
}

老代码:

#include<cstdio>
using namespace std;
#define f(a,b,c) for(int a=b;a<=c;a++)
int main(){
    int n,ans=3;
    int prime[5001]={0},size=0,pf[40001]={0},phi[40001]={0};
    scanf("%d",&n);
    if(n^1){
        f(i,2,n-1){
            if(!pf[i]){
                phi[i]=pf[i]=prime[size++]=i;
                phi[i]--;
            }
            ans+=(phi[i]<<1);
            f(j,0,size-1)
                if((prime[j]*i>n)|(prime[j]>pf[i])) break;
                else{
                    pf[i*prime[j]]=prime[j];
                    if(i%prime[j]) phi[i*prime[j]]=phi[i]*phi[prime[j]];
                    else phi[i*prime[j]]=phi[i]*prime[j];
                }
        }
        printf("%d",ans);
    }
    else putchar('0');
    return 0;
}

Python 3 版代码 (现学现卖,第一个python程序)

n=int(input())
if n==1 :
    print(0)
else:
    prime=[]
    phi=[]
    fc=[]
    for i in range(1,n+1):
        fc.append(0),phi.append(0)
    phi[1]=1
    for i in range(2,n):
        if fc[i]==0 :
            prime.append(i)
            phi[i]=i-1
            fc[i]=i;
        for p in prime :
            if p*i>=n :
                break
            else:
                fc[p*i]=p
                phi[p*i]=p*phi[i]
                if p<fc[i] :
                    phi[p*i]-=phi[i]
                else:
                    break
        phi[i]+=phi[i-1]
    ans=phi[n-1]<<1|1
    print(ans)

最后安利一下 本蒟蒻的博客

【补充证明】

该证明方法需要对集合、容斥原理、概率有一定的了解

同样地,我们再用容斥原理考虑一下 n=p_1^{k_1}p_2^{k_2}p_3^{k_3}\dots p_m^{k_m}

或者本人比较喜欢这样书写:\displaystyle n=\prod_{i=1}^m p_i^{k_i}

其中,保证 \forall i\in[1,m]\bigcap Z 都有 p_i 为质数

方便起见,我们令 U=[1,m]\bigcap Z

那么,n 中与含因数 p_i 的数都与 n 不互质,它们一共有:

扣除后剩余 $\displaystyle [n-\sum_{i=1}^m({n\over p_i})]$ 个数 但是,同时含 $p_ip_j$ 的在 $p_i$ 处扣除了一次, $p_j$ 处又扣除了一次,我们还得补回去,它们一共有: $p_ip_j\ ,\ 2p_ip_j\ ,\ 3p_ip_j\ \dots\ ({n\over p_ip_j})p_ip_j$ 共 $({n\over p_ip_j})$ 个 补充后为 $\displaystyle [n-\sum_{i=1}^m({n\over p_i})+\sum_{i=1}^m\sum_{j=i+1}^m({n\over p_ip_j})]$ 个数 但是,同时含 $p_ip_jp_k$ 的数又被多扣除了一次 你会发现,你扣除了一个数的,就要补充两个数的,接着就要扣除三个数的,然后补充四个数的...... 终于,根据容斥原理,$m$ 个数的不知道是扣除还是补充后,终于,没有任何重复或缺失了,但你也发现按这种方法无法表达。你陷入了一种只可意会不可言传的境界 能领略到欧拉函数的美妙是一件好事,但仅限于那种只可意会不可言传的境界是不够的,我们得想办法表达出来 --- 我们换个角度思考,只考虑 $p_i$ 的时候,我们相当于考虑了 $U$ 的所有单元素子集 只考虑 $p_ip_j$ 的时候,相当于考虑了 $U$ 的所有双元素子集 以此类推,考虑 $\displaystyle \prod_{i=1}^n p_i$ 的时候考虑了 $U$ 的 $n$ 元素子集 合起来,我们实际上考虑了 $U$ 的所有非空子集 那么,我们考虑 $T$ 为 $U$ 的所有非空子集的集合 (注意,$T$ 本身就是集合,但它的元素也是集合,它是集合的集合) 那么,$\displaystyle\boldsymbol\varphi(n)=n+\sum_{S\in T}[(-1)^{Card(S)}({n\over\prod_{i\in S}p_i})]

提取公因式 n ,得到 \displaystyle\boldsymbol\varphi(n)=n[1+\sum_{S\in T}{(-1)^{Card(S)}\over\prod_{i\in S}p_i}]

根据公式 \displaystyle 1+\sum_{S\in T}{[(-1)^{Card(S)}\prod_{i\in S}p_i}]=\prod_{i\in U}(1-p_i) (该公式的证明我再往下放)

我们得到 \displaystyle 1+\sum_{S\in T}{(-1)^{Card(S)}\over\prod_{i\in S}p_i}=1+\sum_{S\in T}{[(-1)^{Card(S)}\prod_{i\in S}({1\over p_i})}]=\prod_{i\in U}(1-{1\over p_i})

因此我们得到 \displaystyle\boldsymbol \varphi(n)=n[1+\sum_{S\in T}{(-1)^{Card(S)}\over\prod_{i\in S}p_i}]=n\prod_{i\in U}(1-{1\over p_i})

根据前面关于 U 的定义 ,我们得到性质5 \displaystyle \boldsymbol \varphi(n)=n\prod_{i=1}^m(1-{1\over p_i})

(累成死狗ing)

关于公式 \displaystyle 1+\sum_{S\in T}{[(-1)^{Card(S)}\prod_{i\in S}p_i}]=\prod_{i\in U}(1-p_i) 怎么来的,这里给出证明

正面证明这个公式是极其困难的,我们考虑用建模的方法证明:

假设有 m 个相互独立的事件,它们发生的概率分别为 p_1\ ,\ p_2\ ,\ p_3\ \dots\ p_m

那么,它们都不发生的概率就是 \displaystyle \prod_{i=1}^m(1-p_i)

根据对 U 的定义,它等于等式的右边

那么左边是什么呢?左边其实就是这个用容斥原理的展开

首先是 1 ,然后减去事件 i 必定发生的概率,再补回 i,j 事件必定发生的概率,再再扣除事件 i,j,k 必定发生的概率......

显然,我们用 3b1b 的方法证明了该式的正确性

(完结撒花)

update on 2023.09.06

写这篇文章的时候我应该是高一高二,现在我已经研一了,时间过得真快

鉴于有人在评论区表示“考场上想不到”、“代码效率低”、“理解难度大”、“看不懂”

确实,这个基于欧拉函数性质的筛法,其实并不适用于绝大部分的的积性函数;这里我们介绍一个适用性更广的线性筛法。其只需要用到性质2、6;即积性函数在质数(及其幂次)的结果,以及积性,这两个性质:

我们考虑在线筛积性函数的时候,除了维护其最小质因子,额外维护其除了最小质因子,是从哪个数字转移来的(不妨称为前继),记为 fr_n

例如 fr_{12}=fr_{2^2\times 3}=3, fr_{30}=fr_{2\times 3\times 5}=15,fr_{8}=fr_{2^3}=1

那么,我们就将数字 n 成功地划分为了两部分:最小质因子的幂次 {n\over fr_n},以及剩余的、与它互质的前继部分 fr_n

基于此,积性函数可以直接转移:

\boldsymbol \varphi(n)=\boldsymbol \varphi({n\over fr_n})\cdot \boldsymbol \varphi(fr_n)

而我们仅需根据性质2直接递推积性函数在质数幂次的结果即可,代码如下:

int minp[MAXN];//最小质因子
int fr[MAXN];//前继
int prime[MAXN], cntprime;//质数列表
int phi[MAXN];//积性函数
inline void sieve_prime(int p, int n) {//处理质数的幂次
    prime[++cntprime]=p;//记录新质数
    phi[p]=p-1;//记录质数处的结果
    minp[p]=p;//记录最小质因子
    fr[p]=1;//记录从1转移而来

    /*
     这里我们为了防止溢出,则 p**k<=n
     可以转化为 p**(k-1)<=n/p
     以下p1表示p**(k-1), p2表示p**k
    */
    for(int p1=p, p2=p*p, pn=n/p; p1<=pn; p1=p2, p2*=p) {
        phi[p2]=phi[p1]*p;//转移质数的幂次
        minp[p2]=p;
        fr[p2]=1;
    }
}
inline void sieve(int n) {
    phi[1]=1;
    for(int i=2; i<=n; ++i) {
        if(!minp[i])//质数
            sieve_prime(i, n);
        for(int j=1, x; j<=cntprime; ++j)
            if(prime[j]>minp[i] || (x=prime[j]*i)>n)
                break;
            else {
                minp[x]=prime[j];//记录最小质因子
                fr[x]=(minp[i] == prime[j]?fr[i]:i);
                /*
                 当i的最小质因子和选中的质数相同时,x的前继和i的前继相同;
                 否则x的前继为i
                */
                phi[x]=phi[x/fr[x]]*phi[fr[x]];//直接利用积性计算
            }
    }
}

我们可以很简单地计算其复杂度:

对于所有的合数,其需要在内层 for 循环中被计算一次,这部分的复杂度是 O(n) 的。

对于所有质数及其幂次,我们会在 sieve_prime 额外计算一次,然而这部分的数字个数是 O(n) 的;又由于我们的转移为 O(1),因此复杂度也是 O(n)

因此,这个实现方法的总复杂度仍然为 O(n),并且其仅需要质数幂次的部分,转移复杂度为 O(1),这个条件显然还适用于莫比乌斯函数、除数个数函数、刘维尔函数等函数。

或者是出题人构造的,奇奇怪怪的积性函数,例如 \boldsymbol f(p^e)=p\oplus e 这种。

此外,对于除数幂和函数 \displaystyle \boldsymbol \sigma_k(n)=\sum_{d\mid n}d^k,有 \boldsymbol \sigma_k(p^e)={1-p^{k(e+1)}\over 1-p^k};其需要先在每个质数处,进行 O(\log k) 的预处理,再进行 O(n) 的转移。

故其复杂度是 O(\pi(n)\log k)=O(n\cdot {\log k\over \log n});只需要保证 \log k 的阶数不超过 \log n,则复杂度依然为 O(n)

因此,我们可以说,这个筛法适合于绝大部分的积性函数;甚至我们可以将积性函数定义为一堆函数构成的数列,乘法就是每个位置对应相乘;这样可以同时筛多个积性函数。

当然,上述说明只是表示递推复杂度是 O(1) 且每个质数的预处理复杂度是 O(\log n) 时,筛法复杂度为 O(n),但这并不是筛法的极限。

假设对于质数 p 而言,递推其每个幂次的平均复杂度为增函数 f(p, n),预处理复杂度为增函数 g(p, n),则总复杂度为:

T(n)&=&O(n)+\sum_{i=1}^{\pi(n)}g(p_i,n)+\sum_{i=1}^{\pi(n)}\sum_{e=1}^{\lfloor{\log_{p_i}n}\rfloor}f(p_i, n)\\ &=&O(n)+O(\int_1^{{n\over \ln n}} g(x\ln x, n)\text dx)+O(\int_1^{n\over \ln n}f(x\ln x, n)\cdot {\ln n\over \ln(x\ln x)}\text dx) \end{aligned}

当然,我的数学水平无法给出这个复杂度达到 O(n) 的条件,但大家可以根据这个公式(或者就基础一点,上面那个求和公式)具体问题具体分析吧......

什么?你看不懂这个公式?那你就感兴理解、别用这个方法,或者直接冲吧。