题解 洛谷P2158 【[SDOI2008]仪仗队】
本文搬自本人洛谷博客
本文进行了一定的更新
- 优化了 Markdown 中 Latex 语句的运用,加强了可读性
- 补充了“我们仍不曾知晓得 消失的 性质5 ”,加强了推导的严谨性
- 介于使用了新的推导方法,调整了推导顺序
- 补充了关于线性筛的欧拉函数性质8
- 又又又又又 修改了部分错误(工程量太大,老是出错)
- 安利了 3b1b 的链接,虽然与本文章无关,但对我们的思维提升很有利
- 将欧拉函数的定义正确修改
- 减少了 \(ans(n)\) 推导公式的争议性,加强了推导过程的严谨性
- 更新了 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\) 的大小
我就是这个地方被卡了好多次......
【代码】
那本蒟蒻就放 我码风极丑的 代码了
新代码:
#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\) 不互质,它们一共有:
\(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 的方法证明了该式的正确性
(完结撒花)