Lucas定理及组合数取模

Posted 青石巷

tags:

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

引入:

组合数C(m,n)表示在m个不同的元素中取出n个元素(不要求有序),产生的方案数。定义式:C(m,n)=m!/(n!*(m-n)!)(并不会使用LaTex QAQ)。

根据题目中对组合数的需要,有不同的计算方法。

(1)在模k的意义下求出C(i,j)(1≤j≤i≤n)共n(数量级)个组合数:

运用一个数学上的组合恒等式(OI中称之为杨辉三角):C(m,n)=C(m-1,n-1)+C(m-1,n)。

证明:

1.直接将组合数化为定义式暴力通分再合并。过程略。

2.运用组合数的含义:设m个元素中存在一个“特殊”元素a,对从m个元素中选出n个元素进行分类讨论。

第一种情况:n个元素中含有元素a,则只需在剩余m-1个元素中选出n-1个元素即可。方案数为C(m-1,n-1)。

第二种情况:n个元素中不含元素a,则只需在剩余m-1个元素中选出n个元素即可。方案数为C(m-1,n)。

这样我们就得到了一个与组合数有关的递推式,初始化C(i,0)=1,随后通过递推以O(n2)的复杂度完成计算。均摊O(1)。

例题:NOIP2016 D2T1 组合数问题 题目链接

题意:给定一个数k,然后给出t组m,n,对于每一组数据,询问对于C(i,j)(0≤i≤n,0≤j≤min(i,m)),有多少个C(i,j)是k的倍数。

数据范围:k≤21,m,n≤2000,t≤10000。子任务见题目链接。

题解:

70分做法:O(20002)预处理出所有组合数,然后每次暴力扫描C(i,j)判断是否是k的倍数。然后机智地忘记取模(没错就是我233)

90分做法:在原有70分做法的预处理中加上取模,暴力扫描判断是否为0。

100分做法:发现每次只是数据范围改变,k和组合数都没有改变,所以尝试优化重复操作。

预处理+取模后,问题变为在整张组合数表中某个范围内0的个数。我们将非0数置0,将0置1,问题转化为矩阵和。用前缀和预处理可以做到O(1)查询。

代码:(将近一年前写的 巨丑)

#include<bits/stdc++.h>
using namespace std;
const int maxn=2000+10;
int c[maxn][maxn],d[maxn][maxn],s[maxn][maxn];
int t,n,m,k;
int main()
{
   int i,j;
   cin>>t>>k;
   for(i=0;i<maxn;i++){c[i][0]=c[i][i]=1;}
   for(i=1;i<maxn;i++){for(j=1;j<i;j++){c[i][j]=(c[i-1][j-1]+c[i-1][j])%k;}}
   for(i=0;i<maxn;i++){for(j=i+1;j<maxn;j++){c[i][j]=1;}}
   for(i=0;i<maxn;i++){for(j=0;j<maxn;j++){if(c[i][j]){d[i][j]=0;}else{d[i][j]=1;}}}
   for(i=0;i<maxn;i++){s[i][0]=s[i-1][0]+d[i][0];s[0][i]=s[0][i-1]+d[0][i];}
   for(i=1;i<maxn;i++){for(j=1;j<maxn;j++){s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+d[i][j];}}
   while(t--)
   {
      int ans=0;
      scanf("%d%d",&n,&m);
      //for(i=0;i<=n;i++){for(j=0;j<=min(i,m);j++){if(!c[i][j]){ans++;}}}
      //cout<<ans<<endl;
      printf("%d\n",s[n][m]);
   }
   return 0;
}

 

以上是关于Lucas定理及组合数取模的主要内容,如果未能解决你的问题,请参考以下文章

组合数取模&&Lucas定理题集

组合数取模

组合数取模 (lucas 定理)

组合数取模(Lucas)

组合数取模 Lucas

大组合数取余模板Lucas定理