P4936 题解

Posted nth-element

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P4936 题解相关的知识,希望对你有一定的参考价值。

吐槽:来自同机房巨佬的嘲讽——这道题这么简单,观察法显然可得通项公式


题目分析:

  1. 分析题面,原题意思如下:

    \(n\)个互不相同的数中取出某些数,将这些数分成两个集合\(A,B\),这两个集合满足两个条件:

    • $ A\(集合中的最大的数小于\)B$集合中最小的数

    • $A,B \neq \emptyset $

    求共有多少种分法(结果对 \(10^9+7\) 取模)

  2. 显然可以发现这题是一道组合数学的题,先手算 \(n \leq 10\) 的数据,在找规律推出公式。我给一下我的思路:

    1. \(n=2\) 时,答案显然为1

    2. \(n=3\) 时,设这些数为 \(a_1,a_2,a_3\) ,不妨设 \(a_1<a_2<a_3\)

      1. 当取3个数时,有以下分法:

        \[A=\a_1 \,B=\a_2,a_3\\]

        \[A=\a_1,a_2\,B=\a_3\\]

        \(C^3_3 \times (3-1)=2\)

      2. 当取2个数时,有以下分法:

        \[A=\a_1\,B=\a_2\\]

        \[A=\a_1\,B=\a_3\\]

        \[A=\a_2\,B=\a_3\\]

        \(C_3^2 \times (2-1)=3\)

      综上,当 \(n=3\) 时,有 \(C^3_3 \times (3-1)+C_3^2 \times (2-1)=5\) 种分法。

    3. \(n=4\) 时,设这些数为 \(a_1,a_2,a_3,a_4\) ,不妨设 \(a_1<a_2<a_3<a_4\)

      1. 当取4个数时,有以下分法:

        \[A=\a_1\,B=\a_2,a_3,a_4\\]

        \[A=\a_1,a_2\,B=\a_3,a_4\\]

        \[A=\a_1,a_2,a_3\,B=\a_4\\]

        \(C^4_4 \times (4-1)=3\)

      2. 当取3个数时,有以下分法:

        \[A=\a_1\,B=\a_2,a_3\\]

        \[A=\a_1\,B=\a_2,a_4\\]

        \[A=\a_1\,B=\a_3,a_4\\]

        \[A=\a_2\,B=\a_3,a_4\\]

        \[A=\a_1,a_2\,B=\a_3\\]

        \[A=\a_1,a_2\,B=\a_4\\]

        \[A=\a_1,a_3\,B=\a_4\\]

        \[A=\a_2,a_3\,B=\a_4\\]

        \(C^3_4 \times (3-1)=8\)

      3. 当取2个数时,有以下分法:

        \[A=\a_1\,B=\a_2\\]

        \[A=\a_1\,B=\a_3\\]

        \[A=\a_1\,B=\a_4\\]

        \[A=\a_2\,B=\a_3\\]

        \[A=\a_2\,B=\a_4\\]

        \[A=\a_3\,B=\a_4\\]

        \(C^2_4 \times (2-1)=6\)

      综上,当 \(n=4\) 时,有 \(C^4_4 \times (4-1)+C^3_4 \times (3-1)+C^2_4 \times (2-1)=17\) 种分法。

    4. 通过数学归纳(找规律),可得公式:

      \[F(n)=\sum_i=1^n-1C^i+1_n \times i\]

    具体实现的代码就不写了,其他dalao有写。

    时间复杂度:\(O(n^2)\)

    空间复杂度:\(O(n^2)\)

    预计得分:40 points

  3. 考虑优化。

    发现公式中的瓶颈在于如何快速处理组合数,于是开始推递推公式:

    \[F(n+1)-F(n)\times2 =\sum_i=1^nC^i+1_n+1 \times i-(\sum_i=1^n-1C^i+1_n \times i)\times2 =\sum_i=1^nC^i_n+1=2^n-1\]

    故得:\(F(n+1)=F(n)\times 2+2^n-1\)

    代码就不写了

    时间复杂度:\(O(n)\)

    空间复杂度:\(O(n)\)

    预计得分:60 points

  4. 继续考虑优化。

    已经写出递推公式的话有两种思路:1. 数学方法推通项公式;2. 矩阵快速幂加速递推公式。

    • 方法一:数学方法推通项公式:

      不会的详见必修五:数列递推求通项或询问数竞同学

      最后推出来的通项公式为:\(F(n)=(n-2)*2^n-1+1\) (若此处实在不知道如何推出来的,可以看一看其他人的题解)

      这里 \(2^n-1\) 用快速幂实现(注意,不能用 \(<<\) 来做,会爆 long long)

      代码见code1

      时间复杂度:\(O(\textlogn)\)

      空间复杂度:\(O(1)\)

      预计得分:100 points

    • 方法二:矩阵快速幂优化:

      个人认为这个方法可能鲜有人能想到(太麻烦了),我就仔细讲一讲。

      首先,观察递推公式中有如下几个影响值:\(F(n),2^n,-1\) ,故与单位矩阵相乘的矩阵为

      \[\beginbmatrix F(n-1)\\ 2^n-1\\ 1\\ \endbmatrix\]

      得到的矩阵为

      \[\beginbmatrix F(n)\\ 2^n\\ 1\\ \endbmatrix\]

      通过一顿乱搞(不会矩阵加速的可以做一做 P1939 【模板】矩阵加速(数列)P1962 斐波那契数列),推出单位矩阵:

      \[\beginbmatrix 2&1&-1\\ 0&2&0\\ 0&0&1\\ \endbmatrix\]

      故矩阵递推式为:

      \[\beginbmatrix F(n-1)\\ 2^n-1\\ 1\\ \endbmatrix \times \beginbmatrix 2&1&-1\\ 0&2&0\\ 0&0&1\\ \endbmatrix =\beginbmatrix F(n)\\ 2^n\\ 1\\ \endbmatrix\]

      化为矩阵幂形式得:

      \[\beginbmatrix F(2)\\ 2^2\\ 1\\ \endbmatrix \times \beginbmatrix 2&1&-1\\ 0&2&0\\ 0&0&1\\ \endbmatrix^n-2 =\beginbmatrix F(n)\\ 2^n\\ 1\\ \endbmatrix\]

      套魔板即可。

      代码见code2

      时间复杂度:\(O(\textlogn)\)

      空间复杂度:\(O(1)\)

    两种方法的优劣:

    • 第一种方法难算,需要较好的数学基础,但最后实现时时间复杂度在 \(O(1)\)\(O(\textlogn)\) ,常数极小。
    • 第二种方法是数列加速的常用方法,简单易想,但常数较大,本题为常数为27,即在大数据下比第一种方法慢2—3倍

    Code1:

    #include<bits/stdc++.h>
     //#define file
     #define modn (int)(1e9+7)
     #define int long long
     using namespace std;
     int n;
     inline int ksm(int x,int y,int p)
     
         if(y==0) return 1%p;
         if(y==1) return x%p;
         int rst=ksm(x,y/2,p)%p;
         if(y%2==0)
         
             return rst*rst%p;
         else
         
             int tmp=rst*rst%p;
             return tmp*x%p;
         
     
     signed main()
     
         #ifdef file
         freopen("Agent1.in","r",stdin);
         freopen("Agent1.out","w",stdout);
         //F(n)=\sum_i=1^n-1C^(i+1)_n*i
         //F(n)=F(n-1)*2+2^n-1-1
         #endif
         scanf("%lld",&n);
         int ans=(n-2)*ksm(2,n-1,modn)%modn+1;
         printf("%lld\n",ans);
         return 0;
      

    AC,49ms / 800.00KB


    Code2(本代码为我的教练所写,码风可能有些许不同):

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define mod 1000000007
    int jz[3][3] = 2,1,-1,0,2,0,0,0,1, 
        jz1[3][3] = 2,1,-1,0,2,0,0,0,1;
    int base[3][3];
    void cheng()
    
        memset(base,0,sizeof(base));
        for(int i=0;i<3;i++)
        
            for(int j=0;j<3;j++)
            
                for(int k=0;k<3;k++)
                
                    base[i][j] += jz1[i][k]*jz1[k][j];
                    base[i][j] = (base[i][j]+mod)%mod;
                
            
        
         memcpy(jz1,base,sizeof(base)); 
    
    void chengjz()
    
        memset(base,0,sizeof(base));
        for(int i=0;i<3;i++)
        
            for(int j=0;j<3;j++)
            
                for(int k=0;k<3;k++)
                
                    base[i][j] += jz1[i][k]*jz[k][j];
                    base[i][j] = (base[i][j]+mod)%mod;
                
            
        
        memcpy(jz1,base,sizeof(base)); 
    
    void quickpow(int k)
    
        if(k==1) return;
        quickpow(k/2);
        cheng();
        if(k%2!=0) chengjz();
    
    signed main()
    
        int n;
        cin>>n;
        if(n==2)
        
            printf("1");
            return 0;
        
        quickpow(n-2);
        int ans = (jz1[0][0]+ 4*jz1[0][1]%mod + mod + 1*jz1[0][2]) mod;
        cout<<ans;
        return 0;
      

    AC,48ms / 796.00KB

以上是关于P4936 题解的主要内容,如果未能解决你的问题,请参考以下文章

双周赛 52,单周赛 241 题解

精LintCode领扣算法问题答案:入门

题解目录

[题解]noip杂题题解

BZOJ1798题解 Seq维护序列题解 双tag裸线段树

比赛题解 更新ING