题解 P2158 【[SDOI2008]仪仗队】

JustinRochester

2018-09-03 14:05:57

Solution

[题目](https://www.luogu.org/problemnew/show/P2158) --- 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$ 互质的数的个数 >数 $a$ 与 $b$ 互质,当且仅当 $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$ 终于,我们得到了本题的答案: $\begin{cases} 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$ 的因数只有 $1$ 和 $p$。 在小于 $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$ 但是,这里还有这重复枚举 $p_1p_2\ ,\ 2p_1p_2\ ,\ 3p_1p_2\ ,\ 4p_1p_2\ \dots\ (p_1^{k_1-1} p_2^{k_2-1}-1)p_1p_2$ 共 $(p_1^{k_1-1} p_2^{k_2-1}-1)$ 个数被重复枚举 因此,根据容斥原理,我们可以得到 $\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$ 时,令 $k$ 为 $i$ 中不含 $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$ 的大小 我就是这个地方被卡了好多次...... --- **【代码】** -- 那本蒟蒻就放 ~~我码风极丑的~~ 代码了 新代码: ```cpp #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; } ``` 老代码: ```cpp #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程序)~~ ```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) ``` 最后安利一下 [本蒟蒻的博客](https://www.luogu.org/blog/JustinRochester/#) ---- **【补充证明】** --- 该证明方法需要对集合、容斥原理、概率有一定的了解 同样地,我们再用容斥原理考虑一下 $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$ 不互质,它们一共有: $p_i\ ,\ 2p_i\ ,\ 3p_i\ \dots\ ({n\over p_i})p_i$ 共 $({n\over p_i})$ 个 扣除后剩余 $\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})]$ >$Card(S)$ 表示集合 $S$ 的元素的数量 提取公因式 $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](https://space.bilibili.com/88461692?from=search&seid=3760714434062012953) 的方法证明了该式的正确性 ---- (完结撒花) --- 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直接递推积性函数在质数幂次的结果即可,代码如下: ```cpp 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)$,则总复杂度为: $\begin{aligned} 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)$ 的条件,但大家可以根据这个公式(或者就基础一点,上面那个求和公式)具体问题具体分析吧...... ~~什么?你看不懂这个公式?那你就感兴理解、别用这个方法,或者直接冲吧。~~