zoukankan      html  css  js  c++  java
  • 题解 洛谷P2158 【[SDOI2008]仪仗队】

    本文搬自本人洛谷博客

    题目

    本文进行了一定的更新

    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\) 的大小

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


    【代码】

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

    新代码:

    #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 的方法证明了该式的正确性


    (完结撒花)

  • 相关阅读:
    动画Animation
    ListView配合BaseAdapter
    ListView 搭配SimpleAdapter
    内部类以及内部接口的实例化
    SAP FI模块常用事务代码
    java随机生成字符串和校验
    JAVA版exe可执行加密软件
    微信小程序实现类似JQuery siblings()的方法
    微信小程序实现给循环列表点击添加类(单项和多项)
    微信小程序如何动态增删class类名
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/12230413.html
Copyright © 2011-2022 走看看