【学习笔记】数学知识-组合计数

加法原理(分类计数原理)

  • 若完成一件事的方法有 \(n\) 类,其中第 \(i(1 \le i \le n)\) 类方法包括 \(a_i\) 种不同的方法,且这些方法互不重合,则完成这件事共有 \(\sum\limits_{i=1}^{n}a_i\) 种不同的方法。

乘法原理(分步计数原理)

  • 若完成一件事的步骤有 \(n\) 个,其中第 \(i(1 \le i \le n)\) 个步骤包括 \(a_i\) 种不同的方法,且这些方法互不重合,则完成这件事共有 \(\prod\limits_{i=1}^{n}a_i\) 种不同的方法。

排列

  • \(n\) 个不同元素中依次取出 \(m\) 个不同的元素按照一定的顺序排成一列,产生的不同排列的数量称作排列数,记为 \(A_{n}^{m}\)\(P_{n}^{m}\) 。递推式为 \(A_{n}^{m}=P_{n}^{m}=n(n-1)(n-2) \dots (n-m+1)=\dfrac{n!}{(n-m)!}\)
    • 相同的排列指元素相同且顺序相同。
    • \(m<n\) 时称作选排列;当 \(m=n\) 时称作全排列,由于 \(0!=1\) ,有 \(A_{n}^{n}=n!\) ;当 \(m>n\)\(m<0\) 时,规定 \(A_{n}^{m}=0\)
    • 通常情况下使用 \(A_{n}^{m}\) 代替 \(P_{n}^{m}\)
  • 性质
    • \(A_{n}^{m}=A_{n}^{m-1} \times (n-m+1)\)
  • 应用
    • \(n\) 个不同元素可以重复地取出 \(m\) 个不同的元素按照一定的顺序排成一列,考虑顺序,产生的不同排列的数量为 \(n^m\) ,称作相异元素的可重复选排列。
    • \(n\) 个不同元素依次取出 \(m\) 个不同的元素按照一定的顺序排成一个环形,考虑顺序,产生的不同环的数量为 \(Q_{n}^{m}=\dfrac{A_{n}^{m}}{m}\) ,称作相异元素的圆排列。
      • 相同的环指元素相同且在环上的顺序相同。
      • 证明
        • \(m=n\) 时,考虑对于其中已经排好的一个环,从不同的位置断开,便可得到不同的排列,即 \(Q_{n}^{n} \times n=A_{n}^{n}=n!\) ,解得 \(Q_{n}^{n}=(n-1)!=\dfrac{A_{n}^{n}}{n}\)
        • \(m \ne n\) 时,先从 \(n\) 个元素中选出 \(m\) 个元素,再对选出的 \(m\) 个元素进行圆排列,即 \(Q_{n}^{m}=\dbinom{n}{m}Q_{m}^{m}=\dfrac{n!}{m!(n-m)!} \times(m-1)!=\dfrac{n!}{m \times (n-m)!}=\dfrac{A_{n}^{m}}{m}\)
      • 例题
    • 多重集是指包含重复元素的广义集合。设 \(S= \{ n_{1} \times a_{1},n_{2} \times a_{2}, \dots,n_{k} \times a_{k} \}\) 是由 \(n_{1}\)\(a_{1}\)\(n_{2}\)\(a_{2}\) \(\dots \ n_{k}\)\(a_{k}\) 组成的多重集。则 \(S\) 的全排列个数为 \(\dfrac{A_{\sum\limits_{i=1}^{k}n_{i}}^{\sum\limits_{i=1}^{k}n_{i}}}{\prod\limits_{i=1}^{k}A_{n_{i}}^{n_{i}}}=\dfrac{(\sum\limits_{i=1}^{k}n_{i})!}{\prod\limits_{i=1}^{k}n_{i}!}\)
  • 例题

组合

  • \(n\) 个不同元素中依次取出 \(m\) 个不同的元素组成一个集合,不考虑顺序,产生的不同集合的数量称作组合数,记为 \(C_{n}^{m}\)\(\dbinom{n}{m}\) ,递推式为 \(C_{n}^{m}=\dbinom{n}{m}=\dfrac{A_{n}^{m}}{A_{m}^{m}}=\dfrac{n!}{m!(n-m)!}\)
    • 相同的组合指元素相同。
    • \(m>n\)\(m<0\) 时,规定 \(\dbinom{n}{m}=0\)
  • 性质
    • \(p \in \mathbb{P}\) ,则对于任意 \(i(1 \le i \le p-1)\) 均满足 \(p|\dbinom{p}{i}\)

      • 证明:首先由于 \(1 \le i \le p-1\) ,有 \(\dbinom{p}{i}=\dfrac{\prod\limits_{j=p-i+1}^{p}j}{i!}\) 为正整数,又因为 \(\gcd(p,i!)=1\) ,则 \(i!| \prod\limits_{j=p-i+1}^{p-1}j\) ,此时有 \(p|\dbinom{p}{i}\)
    • \(\dbinom{n}{m}=\dbinom{n}{n-m}\)

    • \(\dbinom{n}{m}=\dfrac{n}{m} \dbinom{n-1}{m-1}\)

      • 证明
        • 将式子拆开即可,有 \(\begin{aligned} \dfrac{n}{m} \dbinom{n-1}{m-1} &=\dfrac{(n-1)!n}{(m-1)!(n-m)!m} \\ &=\dfrac{n!}{m!(n-m)!} \\ &=\dbinom{n}{m} \end{aligned}\)
    • \(\dbinom{n}{m}=\dbinom{n-1}{m}+\dbinom{n-1}{m-1}\)

      • 证明
        • 将式子拆开即可,有 \(\begin{aligned} \dbinom{n-1}{m}+\dbinom{n-1}{m-1} &=\dfrac{(n-1)!}{m!(n-1-m)!}+\dfrac{(n-1)!}{(m-1)!(n-m)!} \\ &=\dfrac{(n-1)!(n-m)}{m!(n-m)!}+\dfrac{(n-1)!m}{m!(n-m)!} \\ &=\dfrac{n!}{m!(n-m)!} \\ &=\dbinom{n}{m} \end{aligned}\)
        • 从二项式定理和杨辉三角的角度考虑,此结论是显然的。
    • \(\dbinom{n}{k}\dbinom{k}{m}=\dbinom{n}{m}\dbinom{n-m}{k-m}\)

      • 证明
        • 将式子拆开即可。
    • \(\sum\limits_{i=0}^{n}\dbinom{n}{i}=2^{n}\)

      • 证明:从二项式定理的角度考虑,取 \(a=b=1\) 的情况即可。
    • \(\sum\limits_{i=0}^{n}(-1)^{i}\dbinom{n}{i}=[n=0]\)

      • 证明:从二项式定理的角度考虑,取 \(a=-1,b=1\) 的情况即可。
    • \(\sum\limits_{i=0}^{n}[i \bmod 2=0]\dbinom{n}{i}=\sum\limits_{i=0}^{n}[i \bmod 2=1]\dbinom{n}{i}=2^{n-1}\)

      • luogu P3414 SAC#1
      • 证明
        • 由于 \(\dbinom{n}{i}=\dbinom{n-1}{i}+\dbinom{n-1}{i-1}\) ,故 \(\sum\limits_{i=0}^{n}[i \bmod 2=0]\dbinom{n}{i}=\sum\limits_{i=0}^{n}[i \bmod 2=1]\dbinom{n}{i}=\sum\limits_{i=0}^{n-1}\dbinom{n}{i}=2^{n-1}\)
    • \(\sum\limits_{i=0}^{k}\dbinom{n}{i}\dbinom{m}{k-i}=\dbinom{n+m}{k}\)

      • 证明
        • 从组合意义的角度分析,此结论是显然的。
    • \(\sum\limits_{i=0}^{n}\dbinom{n}{i}\dbinom{n}{i}=\dbinom{2n}{n}\)

    • \(\sum\limits_{i=1}^{n}\dbinom{n-i+1}{i}=Fib_{n+2}\)

    • \(\sum\limits_{i=1}^{n}\dbinom{n-i}{i}=Fib_{n+1}\)

      • 证明
        • 推式子,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\dbinom{n-i}{i} &=\sum\limits_{i=1}^{n-1}\dbinom{n-1-i+1}{i}+\dbinom{n-n}{n} \\ &=Fib_{n+1}+0 \\ &=Fib_{n+1} \end{aligned}\)
  • 组合数的求法
    • 利用杨辉三角求解,用来解决给定 \(p\) ,多组询问,每次给定较小的 \(n,m\) ,求 \(\dbinom{n}{m} \bmod p\)

    • 现算阶乘和阶乘的逆元,用来解决多组询问,每次给定 \(n,m,p\) ,求 \(\dbinom{n}{m} \bmod p\) ,常搭配 \(Lucas\) 一起使用。

      点击查看代码
      ll C(ll n,ll m,ll p)
      {
          ll a=1,b=1,i;
          if(n>=m&&n>=0&&m>=0)
          {
              for(i=n-m+1;i<=n;i++)
              {
                  a=a*i%p;
              }
              for(i=1;i<=m;i++)
              {
                  b=b*i%p;
              }
              return a*qpow(b,p-2,p)%p;
          }
          else
          {
              return 0;
          }
      }
      
    • 预处理逆元,阶乘,阶乘的逆元,用来解决给定 \(p\) ,多组询问,每次询问给定 \(n,m\)\(\dbinom{n}{m} \bmod p\)

      • luogu B3717 组合数问题

        点击查看代码
        ll C(ll n,ll m,ll p)
        {
            return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p:0;
        }
        inv[1]=1;
        jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
        for(i=2;i<=N;i++)
        {
            inv[i]=(p-p/i)*inv[p%i]%p;
            jc[i]=jc[i-1]*i%p;
            jc_inv[i]=jc_inv[i-1]*inv[i]%p;
        }
        
    • 给定较大数 \(n,m\) ,求 \(\dbinom{n}{m}\) ,且需要高精度时。预处理素数,将分子、分母快速分解质因数,并保存各项质因子的指数,然后将分子、分母各个质因子的指数对应相减,即把分母消去,最后把剩余质因子乘起来。

  • 应用
    • 错位排列
      • 错位排列是指没有任何元素出现在其有序位置的排列。即对于 \(1 \sim n\) 的排列 \(P\) ,如果不存在一个 \(i(1 \le i \le n)\) 满足 \(P_i=i\) ,则称 \(P\)\(n\) 的错位排列。记 \(D_{n}\) 表示 \(n\) 个元素的错位排列个数。递推式为 \(D_{n}=\begin{cases}0 & n=1 \\1 & n=2 \\ (n-1)(D_{n-1}+D_{n-2}) & otherwise\end{cases}\) 。特别的,当 \(n \ne 1\) 时,有 \(D_{n}=nD_{n-1}+(-1)^{n}\)

        • 证明
          • \(n=1\)\(n=2\) 时,显然。
          • \(n \ne 1\)\(n \ne 2\) 时,首先,设把第 \(n\) 个元素放在第 \(k(k \ne n)\) 个位置,一共有 \(\dbinom{n-1}{1}=n-1\) 种不同的方法。其次,第 \(k\) 个元素放到第 \(n\) 个位置时,一共有 \(D_{n-2}\) 种不同的方法;第 \(k\) 个元素不放到第 \(n\) 个位置时,一共有 \(D_{n-1}\) 种不同的方法。故有 \(D_{n}=(n-1)(D_{n-1}+D_{n-2})\)
          • \(n \ne 1\) 时,设 \(1 \le k \le n-2\) ,有 \(\begin{aligned} D_{n}-nD_{n-1} &=(n-1)(D_{n-1}+D_{n-2})-nD_{n-1} \\ &=(-1)^{1}(D_{n-1}-(n-1)D_{n-2}) \\ &=(-1)^{2}(D_{n-2}-(n-2)D_{n-3}) \\ &=(-1)^{k}(D_{n-k}-(n-k)D_{n-k-1}) \\ &=(-1)^{n-2}(D_{2}-2D_{1}) \\\ &=(-1)^{n} \end{aligned}\) ,移项得 \(D_{n}=nD_{n-1}+(-1)^{n}\)
      • 代码实现

        点击查看代码
        d[1]=0;
        d[2]=1;
        for(i=3;i<=n;i++)
        {
        	d[i]=(i-1)*(d[i-1]+d[i-2]);
        }
        cout<<d[n]<<endl;
        
      • 例题

        • luogu P1595 信封问题
        • UVA12024 Hats
        • luogu P3182 [HAOI2016]放棋子
          • 将障碍转化成错排挺显然的。 \(D_{n}\) 即为所求。

            点击查看代码
            ll d[201][200],len[201],ans[200];
            void jia(ll c[],ll &lenc,ll a[],ll &lena,ll b[],ll &lenb)
            {
                ll i,x=0;
                lenc=max(lena,lenb);
                for(i=1;i<=lenc;i++)
                {
                    c[i]=a[i]+b[i]+x;
                    if(c[i]>=1000000000000000)
                    {
                        x=c[i]/1000000000000000;
                        c[i]%=1000000000000000;
                    }
                    else
                    {
                        x=0;
                    }
                }
                lenc++;
                c[lenc]=x;
                while(lenc>0&&c[lenc]==0)
                {
                    lenc--;
                }
            }
            void cheng(ll a[],ll &lena,ll b)
            {
                ll i,x=0;
                for(i=1;i<=lena;i++)
                {
                    ans[i]=a[i]*b+x;
                    if(ans[i]>=1000000000000000)
                    {
                        x=ans[i]/1000000000000000;
                        ans[i]%=1000000000000000;
                    }
                    else
                    {
                        x=0;
                    }
                }
                lena++;
                ans[lena]=x;
                while(lena>0&&ans[lena]==0)
                {
                    lena--;
                }
                if(lena==0)
                {
                    lena++;
                    ans[lena]=1;
                }
                for(i=1;i<=lena;i++)
                {
                    a[i]=ans[i];
                }
            }
            int main()
            {
                ll n,i;
                cin>>n;
                len[1]=len[2]=1;
                d[1][1]=0;
                d[2][1]=1;
                for(i=3;i<=n;i++)
                {
                    jia(d[i],len[i],d[i-1],len[i-1],d[i-2],len[i-2]);
                    cheng(d[i],len[i],i-1);
                }
                cout<<d[n][len[n]];
                for(i=len[n]-1;i>=1;i--)
                {
                    printf("%015lld",d[n][i]);
                }   
                return 0;
            }
            
        • luogu P4071 [SDOI2016] 排列计数
          • \(\dbinom{m}{n}D_{n-m}\) 即为所求。

            • 特别地,本题要求 \(D_{0}=1\)
            点击查看代码
            ll inv[1000001],jc[1000001],jc_inv[1000001],d[1000001];
            ll C(ll n,ll m,ll p)
            {
                return (n>=m&&n>=0&&m>=0)?((jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p):0;
            }
            int main()
            {
                ll t,n,m,p=1000000007,i;
                scanf("%lld",&t);
                inv[1]=1;
                jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
                d[0]=1;
                d[1]=0;
                d[2]=1;
                for(i=3;i<=1000000;i++)
                {
                    d[i]=((i-1)%p)*((d[i-1]+d[i-2])%p)%p;
                }
                for(i=2;i<=1000000;i++)
                {
                    inv[i]=(p-p/i)*inv[p%i]%p;
                    jc[i]=jc[i-1]*i%p;
                    jc_inv[i]=jc_inv[i-1]*inv[i]%p;
                }
                for(i=1;i<=t;i++)
                {
                    scanf("%lld%lld",&n,&m);
                    printf("%lld\n",C(n,m,p)*d[n-m]%p);
                }
                return 0;
            }
            
        • CF340E Iahub and Permutations
    • \(S= \{ n_{1} \times a_{1},n_{2} \times a_{2}, \dots,n_{k} \times a_{k} \}\) 是由 \(n_{1}\)\(a_{1}\)\(n_{2}\)\(a_{2}\) \(\dots \ n_{k}\)\(a_{k}\) 组成的多重集。设整数 \(m \le \min\limits_{i=1}^{k} \{ n_{i} \}\) ,则从 \(S\) 中选出 \(m\) 个元素组成一个多重集(不考虑元素顺序),产生的不同多重集的数量为 \(\dbinom{k+m-1}{k-1}\)
    • 卡特兰数
  • 例题
    • SP28304 ADATEAMS - Ada and Teams
    • luogu P1771 方程的解
      • \(\sum\limits_{i=1}^{n}x_i=m\) 的正整数解的数量为 \(\dbinom{m-1}{n-1}\)

        • 等价于求把 \(m\) 个不同的小球分成 \(n\) 个部分,使得每个部分至少有一个小球的方案数。
      • \(\sum\limits_{i=1}^{n}x_i=m\) 的非负整数解的数量为 \(\dbinom{n+m-1}{n-1}\)

        • 等价于求 \(\sum\limits_{i=1}^{n}x_i=n+m\) 的正整数解的数量。
        点击查看代码
        ll num[1001],a[1001],ans[1001],lena=1;
        void cheng(ll a[],ll &lena,ll b)
        {
            ll i,x=0;
            for(i=1;i<=lena;i++)
            {  
                ans[i]=a[i]*b+x;
                if(ans[i]>=1000000000000000)
                {
                    x=ans[i]/1000000000000000;
                    ans[i]%=1000000000000000;
                }
                else
                {
                    x=0;
                }
            }
            lena++;
            ans[lena]=x;
            while(lena>0&&ans[lena]==0)
            {
                lena--;
            }
            if(lena==0)
            {
                lena++;
                ans[lena]=1;
            }
            for(i=1;i<=lena;i++)
            {
                a[i]=ans[i];
            }
        }
        void divide(ll n,ll pd)
        {
            ll i,sum=0;
            for(i=2;i<=sqrt(n);i++)
            {
                if(n%i==0)
                {
                    sum=0;
                    while(n%i==0)
                    {
                        n/=i;
                        sum++;
                    }
                    num[i]+=pd*sum;
                }
            }
            if(n>1)
            {
                num[n]+=pd;
            }
        }
        ll qpow(ll a,ll b,ll p)
        {
            ll ans=1;
            while(b>0)
            {
                if(b&1)
                {
                    ans=ans*a%p;
                }
                b>>=1;
                a=a*a%p;
            }
            return ans;
        }
        void C(ll n,ll m)
        {
            ll i,j;
            if(n>=m)
            {
                for(i=n-m+1;i<=n;i++)
                {
                    divide(i,1);
                }
                for(i=1;i<=m;i++)
                {
                    divide(i,-1);
                }
                for(i=1;i<=n;i++)
                {
                    if(num[i]!=0)
                    {
                        for(j=1;j<=num[i];j++)
                        {
                            cheng(a,lena,i);
                        }
                    }
                }
            }
        }
        int main()
        {
            ll k,x,p=1000,i;
            cin>>k>>x;
            a[1]=1;
            C(qpow(x,x,p)-1,k-1);
            cout<<a[lena];
            for(i=lena-1;i>=1;i--)
            {
                printf("%015lld",a[i]);
            }   
            return 0;
        }
        
    • luogu P1350 车的放置
      • 将棋盘分割为 \(a \times (b+d)\)\(c \times d\) 的矩阵,因为 \(a \times (b+d)\)\(c \times d\) 的矩阵放置的车有冲突情况,故 \(\sum\limits_{i=0}^{n}\dbinom{a}{i}\dbinom{b+d-(k-i)}{i}A_{i}^{i}\dbinom{c}{k-i}\dbinom{d}{k-i}A_{k-i}^{k-i}\) 即为所求。
    • luogu P2606 [ZJOI2010] 排列计数
      • 对于常见的计数 \(DP\) ,记录是从哪个元素转移来的会耗费大量的空间,且无法确定是否合法。因此就需要利用离散化的思想来实现“等效代换”,记录其相对的大小关系。

      • 容易发现满足性质的序列是一个小根堆。

      • \(s_i\) 表示以 \(i\) 为根的小根堆的大小, \(f_i\) 表示以 \(i\) 为根的小根堆的合法方案数。

      • 因为要求自己是最小的,故需要在 \(s_i-1\) 个中选出 \(s_{2i}\) 个给左儿子,剩下的 \(s_{2i+1}\) 个给有右儿子。

      • 故得到转移方程 \(s_i=s_{2i}+s_{2i+1}+1,f_i=\dbinom{s_{2i}}{s_i-1}f_{2i}f_{2i+1}\) 。最后, \(f_1\) 即为所求。

        • 此时该小根堆内记录的不是实际元素,而是其之间的大小关系。
          • 这里如果不理解,可以自己带几组样例模拟一下。
        点击查看代码
        ll inv[3000001],jc[3000001],jc_inv[3000001],f[3000001],s[3000001];
        ll C(ll n,ll m,ll p)
        {
            if(n>=m&&n>=0&&m>=0)
            {
                return (jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p;
            }
            else
            {
                return 0;
            }
        }
        ll lucas(ll n,ll m,ll p)
        {
            return m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1;
        }
        int main()
        {
            ll n,p,i;
            cin>>n>>p;
            inv[1]=1;
            jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
            for(i=2;i<=min(n,p-1);i++)
            {
                inv[i]=(p-p/i)*inv[p%i]%p;
                jc[i]=jc[i-1]*i%p;
                jc_inv[i]=jc_inv[i-1]*inv[i]%p;
            }
            for(i=1;i<=2*n+1;i++)
            {
                f[i]=1;
            }
            for(i=n;i>=1;i--)
            {
                s[i]=s[i*2]+s[i*2+1]+1;
                f[i]=(lucas(s[i]-1,s[i*2],p)*f[i*2]%p)*f[i*2+1]%p;
            }
            cout<<f[1]<<endl;
            return 0;
        }
        
    • luogu P6191 [USACO09FEB] Bulls And Cows S | HZOJ 720.种树
    • LibreOJ 10235. 「一本通 6.6 练习 6」序列统计
      • \(m=r-l+1\) ,设 \(0 \le k \le n-1\) ,枚举长度 \(i\) ,等价于求 \(\sum\limits_{j=1}^{m}x_j=i\) 的非负整数解的数量。

      • 推式子,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\dbinom{m+i-1}{i} &=\dbinom{m}{0}-1+\sum\limits_{i=1}^{n}\dbinom{m+i-1}{i} \\ &=\dbinom{m}{1}+\dbinom{m}{0}-1+\sum\limits_{i=2}^{n}\dbinom{m+i-1}{i} \\ &=\dbinom{m+1}{1}-1+\sum\limits_{i=2}^{n}\dbinom{m+i-1}{i} \\ &=\dbinom{m+k}{k}-1+\sum\limits_{i=k+1}^{n}\dbinom{m+i-1}{i} \\ &=\dbinom{m+n-1}{n-1}+\dbinom{m+n-1}{n}-1 \\ &=\dbinom{n+m}{n}-1 \end{aligned}\)

        点击查看代码
        ll inv[1000010],jc[1000010],jc_inv[1000010];
        ll C(ll n,ll m,ll p)
        {
            if(n>=m&&n>=0&&m>=0)
            {
                return (jc[n]*jc_inv[m]%p)*jc_inv[n-m]%p;
            }
            else
            {
                return 0;
            }
        }
        ll lucas(ll n,ll m,ll p)
        {
            return m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1;
        }
        int main()
        {
            ll t,n,m,l,r,i,p=1000003;
            cin>>t;
            inv[1]=1;
            jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
            for(i=2;i<=p-1;i++)
            {
                inv[i]=(p-p/i)*inv[p%i]%p;
                jc[i]=jc[i-1]*i%p;
                jc_inv[i]=jc_inv[i-1]*inv[i]%p;
            }
            for(i=1;i<=t;i++)
            {
                cin>>n>>l>>r;
                m=r-l+1;
                cout<<(lucas(n+m,n,p)+p-1)%p<<endl;
            }
            return 0;
        }
        
    • BZOJ3462 DZY Loves Math II
    • luogu P2154 [SDOI2009] 虔诚的墓主人
      • 观察到 \(n,m\) 极大,但 \(w\) 较小,于是考虑进行离散化。

      • 对于横坐标相等, \(y_{i} \le y_{j}\) 的两棵常青树 \(i,j\) ,其相隔的 \(y_{j}-y_{i}\) 个墓地对答案产生的贡献为 \(\dbinom{up_{x_{i}}}{k}\dbinom{down_{x_{i}}}{k}\sum\limits_{h=y_{i}+1}^{y_{j}-1}\dbinom{lift_{h}}{k}\dbinom{right_{h}}{k}\) ,其中 \(up_{l},down_{l},left_{l},right{l}\) 分别表示 \((x,l)\) 上、下、左、右方常青树的个数。

      • \(\dbinom{up_{x_{i}}}{k}\dbinom{down_{x_{i}}}{k}\) 在枚举常青树的过程中即可处理。

      • 考虑用单点修改、区间查询的树状数组维护 \(\sum\limits_{h=y_{i}+1}^{y_{j}-1}\dbinom{lift_{h}}{k}\dbinom{right_{h}}{k}\) 。注意枚举到一棵新的常青树时,要先减去其原来对答案的贡献,重新计算贡献。

      • 细节较多。

        点击查看代码
        struct node
        {
            ll x,y;
        }a[100010];
        ll x[100010],y[100010],c[100010][11],c2[100010][2],l[100010],sum[100010][2];
        bool cmp(node a,node b)
        {
            return (a.x==b.x)?(a.y<b.y):(a.x<b.x);
        }
        ll query(ll a[],ll x)
        {
            return lower_bound(a+1,a+1+a[0],x)-a;
        }
        ll lowbit(ll x)
        {
            return (x&(-x));
        }
        void add(ll n,ll x,ll key,ll p)
        {
            ll i;
            for(i=x;i<=n;i+=lowbit(i))
            {
                c2[i][0]=(c2[i][0]+key+p)%p;
            }
        }
        ll getsum(ll x,ll p)
        {
            ll ans=0,i;
            for(i=x;i>0;i-=lowbit(i))
            {
                ans=(ans+c2[i][0])%p;
            }
            return ans;
        }
        void C(ll n,ll m,ll p)
        {
            c[0][0]=c[1][0]=c[1][1]=1;
            for(ll i=2;i<=n;i++)
            {
                c[i][0]=1;
                for(ll j=1;j<=m;j++)
                {
                    c[i][j]=(c[i-1][j-1]+c[i-1][j])%p;
                }
            }
        }
        int main()
        {
            ll n,m,w,k,u,d=0,r,lr,i,ans=0,p=2147483648;
            cin>>n>>m>>w;
            a[0].x=a[0].y=0;
            for(i=1;i<=w;i++)
            {
                cin>>a[i].x>>a[i].y;
                a[i].x++;
                a[i].y++;
                x[i]=a[i].x;
                y[i]=a[i].y;
            }
            cin>>k;
            sort(x+1,x+1+w);
            sort(y+1,y+1+w);
            x[0]=unique(x+1,x+1+w)-(x+1);
            y[0]=unique(y+1,y+1+w)-(y+1);
            C(w,k,p);
            for(i=1;i<=w;i++)
            {
                a[i].x=query(x,a[i].x);
                a[i].y=query(y,a[i].y);
                sum[a[i].x][0]++;
                sum[a[i].y][1]++;
            }
            sort(a+1,a+1+w,cmp);
            for(i=1;i<=w-1;i++)
            {
                d=((a[i-1].x==a[i].x)?d:0)+1;
                u=sum[a[i].x][0]-d;
                if(d>=k&&u>=k&&a[i].x==a[i+1].x)
                {
                    lr=(a[i+1].y-1>=a[i].y+1)?(getsum(a[i+1].y-1,p)-getsum(a[i].y+1-1,p)+p)%p:0;
                    ans=(ans+(((c[d][k]*c[u][k])%p)*lr)%p)%p;
                }
                add(y[0],a[i].y,-c2[a[i].y][1],p);
                l[a[i].y]++;
                r=sum[a[i].y][1]-l[a[i].y];
                c2[a[i].y][1]=(l[a[i].y]>=k&&r>=k)?(c[l[a[i].y]][k]*c[r][k])%p:0;
                add(y[0],a[i].y,c2[a[i].y][1],p);
            }
            cout<<ans<<endl;
            return 0;
        }
        
    • luogu P2467 [SDOI2010] 地精部落
      • \(f_i\) 表示长度为 \(i\) 的山脉且第一个位置是山谷的合法方案数, \(g_i\) 表示长度为 \(i\) 的山脉且第一个位置是山峰的合法方案数。

        • 此时该山脉内记录的不是实际元素,而是其之间的大小关系。
      • 容易有 \(f_i\)\(f_{i-1}\) 转移过来的过程中,将 \(i\) 加入其中,一定是放在山峰的位置,故枚举插入的位置 \(j(j \bmod 2=0)\) ,从 \(i-1\) 个里面选出 \(j-1\) 个放在插入的 \(i\) 的左边,剩下的 \((i-1)-(j-1)\) 个放在插入的 \(i\) 的右边。 \(g_i\) 同理。

      • 故得到转移方程 \(\begin{cases}f_i=\sum\limits_{j=1}^{i}[j \bmod 2=0] \dbinom{i-1}{j-1}f_{j-1}f_{(i-1)-(j-1)} \\ g_i=\sum\limits_{j=1}^{i}[j \bmod 2=1] \dbinom{i-1}{j-1}g_{j-1}f_{(i-1)-(j-1)} \end{cases}\) 。最终,有 \(f_n+g_n\) 即为所求。

        点击查看代码
        ll c[2][4400],f[4400],g[4400];
        int main()
        {
            ll n,p,i,j;
            cin>>n>>p;
            c[0][0]=c[1][0]=c[1][1]=1;
            f[0]=g[0]=f[1]=g[1]=1;
            for(i=2;i<=n;i++)
            {
                for(j=1;j<=i;j++) 
                {
                    c[i%2][0]=1;
                    if(j%2==0)
                    {
                        f[i]=(f[i]+(((c[(i-1)%2][j-1]*f[j-1])%p)*f[(i-1)-(j-1)])%p)%p;
                    }
                    else
                    {
                        g[i]=(g[i]+(((c[(i-1)%2][j-1]*g[j-1])%p)*f[(i-1)-(j-1)])%p)%p;
                    }
                    c[i%2][j]=(c[(i-1)%2][j-1]+c[(i-1)%2][j])%p;
                }
            }
            cout<<(f[n]+g[n])%p<<endl;
            return 0;
        }
        
    • luogu P4910 帕秋莉的手环
    • luogu P3214 [HNOI2011] 卡农

二项式定理、二项式反演

  • 二项式定理
    • 二项式定理: \((a+b)^n=\sum\limits_{i=0}^{n}\dbinom{n}{i}a^{i}b^{n-i}\)
    • 证明
      • 数学归纳法
        • \(n=1\) 时, \((a+b)^1=\dbinom{1}{0}a^0b^1+\dbinom{1}{1}a^1b^0=a+b\) 成立。
        • 假设当 \(n=m\) 时命题成立,当 \(n=m+1\) 时,有 \(\begin{aligned} (a+b)^{m+1} &=(a+b)(a+b)^m \\ &=(a+b)\sum\limits_{i=0}^{m}\dbinom{m}{i}a^{i}b^{m-i} \\ &=\sum\limits_{i=0}^{m}\dbinom{m}{i}a^{i+1}b^{m-i}+\sum\limits_{i=0}^{m}\dbinom{m}{i}a^{i}b^{m-i+1} \\ &=\sum\limits_{i=1}^{m+1}\dbinom{m}{i-1}a^{i}b^{m-i+1}+\sum\limits_{i=0}^{m}\dbinom{m}{i}a^{i}b^{m-i+1} \\ &=\dbinom{m}{m}a^{m+1}b^{0}+\dbinom{0}{m}a^{0}b^{m+1}+\sum\limits_{i=1}^{m}(\dbinom{m}{i-1}+\dbinom{m}{i})a^{i}b^{m+1-i} \\ &=\dbinom{m+1}{m+1}a^{m+1}b^{0}+\dbinom{0}{m+1}a^{0}b^{m+1}+\sum\limits_{i=1}^{m}\dbinom{m+1}{i}a^{i}b^{m+1-i} \\ &=\sum\limits_{i=0}^{m+1}\dbinom{m+1}{i}a^{i}b^{m+1-i} \end{aligned}\)
        • 证毕。
      • 组合意义
        • \((a+b)^n\) 展开时可以理解为从 \(n\) 个式子 \(a+b\) 中的 \(i(0 \le i \le n)\) 个里取 \(a\) ,剩下的 \(n-i\) 个式子 \(a+b\) 里取 \(b\) ,组成一个乘积项 \(a^{i}b^{n-i}\) ,其系数为 \(\dbinom{n}{i}\)
    • 例题
  • 二项式反演
    • \(f_{n}\) 表示恰好 \(n\) 个元素组成特定结构的方案数, \(g_{n}\) 表示从 \(n\) 个元素中选出 \(i(i \ge 0)\) 个元素组成特定结构(虽然其他元素可能一并组成特定结构,但我们不去管它)的总方案数,即 \(g_{n}=\sum\limits_{i=0}^{n}\dbinom{n}{i}f_{i}\) ,那么有 \(f_{n}=\sum\limits_{i=0}^{n}(-1)^{n-i}\dbinom{n}{i}g_{i}\)
    • 证明
      • 推式子,有 \(\begin{aligned} f_{n} &=\sum\limits_{i=0}^{n}(-1)^{n-i}\dbinom{n}{i}g_{i} \\ &=\sum\limits_{i=0}^{n}(-1)^{n-i}\dbinom{n}{i}(\sum\limits_{j=0}^{i}\dbinom{i}{j}f_{j}) \\ &=\sum\limits_{i=0}^{n}\sum\limits_{j=0}^{i}\dbinom{n}{i}\dbinom{i}{j}(-1)^{n-i}f_{j} \\ &=\sum\limits_{j=0}^{n}\sum\limits_{i=j}^{n}\dbinom{n}{i}\dbinom{i}{j}(-1)^{n-i}f_{j} \\ &=\sum\limits_{j=0}^{n}f_{j}(\sum\limits_{i=j}^{n}\dbinom{n}{i}\dbinom{i}{j}(-1)^{n-i}) \\ &=\sum\limits_{j=0}^{n}f_{j}(\sum\limits_{i=j}^{n}\dbinom{n}{j}\dbinom{n-j}{j}(-1)^{n-i}) \\ &=\sum\limits_{j=0}^{n}\dbinom{n}{j}f_{j}(\sum\limits_{i=j}^{n}\dbinom{n-j}{j}(-1)^{n-i}) \\ &=\sum\limits_{j=0}^{n}\dbinom{n}{j}f_{j}(\sum\limits_{k=0}^{n-j}\dbinom{n-j}{k}(-1)^{n-(j+k)}) \\ &=\sum\limits_{j=0}^{n}\dbinom{n}{j}f_{j}(\sum\limits_{k=0}^{n-j}\dbinom{n-j}{k}(-1)^{n-j-k}) \\ &=\sum\limits_{j=0}^{n}\dbinom{n}{j}f_{j}[n-j=0] \\ &=\sum\limits_{j=0}^{n}\dbinom{n}{j}f_{j}[n=j] \\ &=f_{n} \end{aligned}\)
    • 其他形式
      • \(f_{i}\) 表示 \(n\) 个元素中恰好有 \(i\) 个元素满足特定条件的方案数, \(g_{i}\) 表示 \(n\) 个元素中至少有 \(i\) 个元素满足特定条件的方案数,即 \(g_{m}=\sum\limits_{i=m}^{n}\dbinom{i}{m}f_{i}\) ,那么有 \(f_{m}=\sum\limits_{i=m}^{n}(-1)^{i-m}\dbinom{i}{m}g_{i}\)
    • 例题

卢卡斯定理

  • 卢卡斯定理:若 \(p\) 是质数,则对于任意正整数 \(n,m\) ,有 \(n=(n_kn_{k-1} \dots n_1)_p,m=(m_km_{k-1} \dots m_1)_p\) ,其中对于每一个 \(i(1 \le i \le k)\) 均满足 \(0 \le n_i,m_i \le p-1\),则 \(\dbinom{n}{m} \equiv \prod\limits_{i=1}^{k}\dbinom{n_i}{m_i}\pmod{p}\)

    • 另一种写法为 \(\dbinom{n}{m} \equiv \dbinom{n \bmod p}{m \bmod p}\dbinom{\left\lfloor \frac{n}{p} \right\rfloor}{\left\lfloor \frac{m}{p} \right\rfloor} \pmod{p}\) .
  • 证明

    • 考虑对于多项式 \((1+x)^n\) ,展开后 \(x^m\) 的系数为 \(\dbinom{n}{m}\)
    • 此时有 \(\begin{aligned} (1+x)^n &=\prod\limits_{i=1}^{k}(1+x)^{p^{i-1} \times n_i} \pmod{p} \\ &=\prod\limits_{i=1}^{k}((1+x)^{p^{i-1}})^{n_i} \pmod{p} \\ &=\prod\limits_{i=1}^{k}(\sum\limits_{j=0}^{p^{i-1}}\dbinom{n}{j}x^{j})^{n_i} \pmod{p} \\ & \equiv \prod\limits_{i=1}^{k}(1+x^{p^{i-1}})^{n_i} \pmod{p} \\ &= \prod\limits_{i=1}^{k}\sum\limits_{j=0}^{n_i}\dbinom{n_i}{j}x^{p^{i-1}j} \pmod{p} \end{aligned}\)
    • \(p\) 进制数的性质,将同余右边展开后有 \(x^m\) 的系数为 \(\prod\limits_{i=1}^{k}\dbinom{n_i}{m_i}\)
    • 证毕。
  • 性质

    • \(\dbinom{n}{m}\) 为奇数当且仅当在二进制表示下 \(n\) 的每一个数位的数都不小于 \(m\) 的相应数位的数,即 \(n \And m=m\)
  • 代码实现

    点击查看代码
    ll lucas(ll n,ll m,ll p)
    {
        return (n>=m&&n>=0&&m>=0)?(m?C(n%p,m%p,p)*lucas(n/p,m/p,p)%p:1):0;
    }
    
  • 例题

扩展卢卡斯定理

容斥原理

  • 容斥原理:设 \(S= \{ S_{1},S_{2},S_{3}, \dots ,S_{n} \}\) ,则有 \(|\bigcup\limits_{i=1}^{n}S_{i}|=\sum\limits_{T \subseteq S}(-1)^{|T|+1} \times |\bigcap\limits_{i=1}^{|T|}T_{i}|\)
  • 应用
    • \(S= \{ n_{1} \times a_{1},n_{2} \times a_{2}, \dots,n_{k} \times a_{k} \}\) 是由 \(n_{1}\)\(a_{1}\)\(n_{2}\)\(a_{2}\) \(\dots \ n_{k}\)\(a_{k}\) 组成的多重集。设整数 \(m \le \sum\limits_{i=1}^{k}n_{i}\) ,则从 \(S\) 中选出 \(m\) 个元素组成一个多重集(不考虑元素顺序),产生的不同多重集的数量为 \(\dbinom{k+m-1}{k-1}-\sum\limits_{T \subseteq \{ n_{1},n_{2}, \dots,n_{k} \},|T| \ge 1}(-1)^{|T|+1} \times \dbinom{k+m-1-\sum\limits_{i=1}^{|T|}(T_{i}+1)}{k-1}\) ,合并得 \(\sum\limits_{T \subseteq \{ n_{1},n_{2}, \dots,n_{k} \}}(-1)^{|T|} \times \dbinom{k+m-1-\sum\limits_{i=1}^{|T|}(T_{i}+1)}{k-1}\)
      • 证明
        • 等价于求 \(\sum\limits_{i=1}^{k}x_{i}=m(x_{i} \le n_{i})\) 的不同方案数。
        • 正难则反,考虑合法方案数等于总方案数减不合法方案数。
        • 总方案数同 luogu P1771 方程的解 ,即 \(\dbinom{k+m-1}{k-1}\)
        • 难点在于如何求不合法方案数。设 \(S_{i}(1 \le i \le k)\) 表示至少包含 \(n_{i}+1\)\(a_{i}\) 的多重集,则 \(S_{i}\) 的方案数为 \(\dbinom{k+m-1-(n_{i}+1)}{k-1}\) 。接着容斥原理,得出 \(|\bigcup\limits_{i=1}^{k}S_{i}|=\sum\limits_{T \subseteq \{ n_{1},n_{2}, \dots,n_{k} \},|T| \ge 1}(-1)^{|T|+1} \times |\bigcap\limits_{i=1}^{|T|}T_{i}|\) ,其方案数为 \(\sum\limits_{T \subseteq \{ n_{1},n_{2}, \dots,n_{k} \},|T| \ge 1}(-1)^{|T|+1} \times \dbinom{k+m-1-\sum\limits_{i=1}^{|T|}(T_{i}+1)}{k-1}\)
        • 二者相减即可得到原式。
  • 例题
posted @ 2023-11-10 20:31  hzoi_Shadow  阅读(108)  评论(5编辑  收藏  举报
扩大
缩小