引入:

组合数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)查询。

代码:

 1 #include<bits/stdc++.h>
2 using namespace std;
3 const int maxn=2000+10;
4 int c[maxn][maxn],d[maxn][maxn],s[maxn][maxn];
5 int t,n,m,k;
6 int main()
7 {
8 int i,j;
9 cin>>t>>k;
10 for(i=0;i<maxn;i++){c[i][0]=c[i][i]=1;}
11 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;}}
12 for(i=0;i<maxn;i++){for(j=i+1;j<maxn;j++){c[i][j]=1;}}
13 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;}}}
14 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];}
15 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];}}
16 while(t--)
17 {
18 int ans=0;
19 scanf("%d%d",&n,&m);
20 //for(i=0;i<=n;i++){for(j=0;j<=min(i,m);j++){if(!c[i][j]){ans++;}}}
21 //cout<<ans<<endl;
22 printf("%d\n",s[n][m]);
23 }
24 return 0;
25 }

(2)在模p的意义下求出C(n,i)(0≤i≤n<p)共n个组合数:

此时在组合数之间无法递推,只能用定义式计算:C(n,i)=n!/(i!*(n-i)!)。

O(n)求出1~n的逆元,并通过前缀积O(n)求得1~n的阶乘及其逆元,对于每个组合数O(1)计算。

并不能算例题的例题:Luogu T7468 I liked Matrix! 题目链接

题目描述:在一个n*m 的矩阵A 的所有位置中随机填入0 或1,概率比为x : y。令B[i]=a[i][1]+a[i][2]+......+a[i][m],求min{B[i]}的期望,并将期望乘以(x + y)^nm 后对1e9+7取模。

数据范围:

对于20% 的数据:n,m,x,y<=3

对于40% 的数据:n,m,x,y<= 70

对于70% 的数据:n,m,x,y<=5000

对于100% 的数据:n,m,x,y<=200000

题解:

20%的直接暴力枚举每个格子x次0,y次1,求出每次min{B[i]}并求和。

40%和70%好像可以用不同的DP来做,DP蒟蒻表示不会......

100分做法:先挖个坑在这 到时候写一个解题报告(虽然并不会写部分分)

(3)在模p(p为质数)的意义下求出C(n,m)(2≤p≤n,m)

此时n,m一般可以出到max long long,简单地O(n)预处理无法接受。就算n,m较小,p比n,m小时定义式中n!和m!在模p的意义下都为0,由于0不存在逆元,故直接用逆元计算会出错。此时需要用Lucas定理递归计算。

Lucas定理:

C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)。第一部分递归计算,第二部分用逆元处理。特别地,当递归过程中出现C(n,m),m>n的情况时,规定C(n,m)=0。

这个定理的另一个表述:C(n,m)%p等价于将n,m写成p进制,对n,m上的每一位进行组合数运算。

例题:Luogu P3807 【模板】卢卡斯定理 题目链接

题解:裸的Lucas。

代码:

 1 #include<bits/stdc++.h>
2 #define LL long long
3 using namespace std;
4 const int maxn=2e5+10;
5 LL fac[maxn],infac[maxn],inv[maxn];
6 int n,m,p,t;LL ans;
7 LL f(int n,int m,int p)
8 {
9 if(m>n){return 0;}
10 return fac[n]*infac[n-m]%p*infac[m]%p;
11 }
12 LL lucas(int n,int m,int p)
13 {
14 if(!m){return 1;}
15 return f(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
16 }
17 int main()
18 {
19 int i,j;
20 cin>>t;
21 while(t--)
22 {
23 cin>>n>>m>>p;
24 fac[0]=infac[0]=1;infac[1]=inv[1]=1;
25 for(i=1;i<p;i++){fac[i]=i%p*fac[i-1]%p;}
26 for(i=2;i<p;i++){inv[i]=(p-p/i*inv[p%i]%p)%p;}
27 for(i=2;i<p;i++){infac[i]=inv[i]*infac[i-1]%p;}
28 printf("%lld\n",lucas(n+m,m,p));
29 }
30 return 0;
31 }

(4)在模p(p不一定为质数)的意义下求出C(n,m):

此时p不一定为质数,无法直接运用Lucas定理求解,可以将p进行质因数分解,对每个质因子进行Lucas,到时候再用中国剩余定理合并。质因数分解建议采用Pollard Rho算法。

组合数取模及Lucas定理的更多相关文章

  1. 大组合数取模之lucas定理模板,1<=n<=m<=1e9,1<p<=1e6,p必须为素数

    typedef long long ll; /********************************** 大组合数取模之lucas定理模板,1<=n<=m<=1e9,1&l ...

  2. 组合数取模(lucas定理+CRT合并)(AC)

    #include<bits/stdc++.h> #define re register #define int long long using namespace std; ; inlin ...

  3. 组合数取模介绍----Lucas定理介绍

    转载https://www.cnblogs.com/fzl194/p/9095177.html 组合数取模方法总结(Lucas定理介绍) 1.当n,m都很小的时候可以利用杨辉三角直接求. C(n,m) ...

  4. bzoj1951 组合数取模 中国剩余定理

    #include<bits/stdc++.h> using namespace std; typedef long long ll; const int a[4]={2,3,4679,35 ...

  5. Codeforces 57C (1-n递增方案数,组合数取模,lucas)

    这个题相当于求从1-n的递增方案数,为C(2*n-1,n); 取模要用lucas定理,附上代码: #include<bits/stdc++.h> using namespace std; ...

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

    题集链接: https://cn.vjudge.net/contest/231988 解题之前请先了解组合数取模和Lucas定理 A : FZU-2020  输出组合数C(n, m) mod p (1 ...

  7. Uva12034 (组合数取模)

    题意:两匹马比赛有三种比赛结果,n匹马比赛的所有可能结果总数 解法: 设答案是f[n],则假设第一名有i个人,有C(n,i)种可能,接下来还有f(n-i)种可能性,因此答案为 ΣC(n,i)f(n-i ...

  8. 组合数取模Lucas定理及快速幂取模

    组合数取模就是求的值,根据,和的取值范围不同,采取的方法也不一样. 下面,我们来看常见的两种取值情况(m.n在64位整数型范围内) (1)  , 此时较简单,在O(n2)可承受的情况下组合数的计算可以 ...

  9. hdu 3944 DP? 组合数取模(Lucas定理+预处理+帕斯卡公式优化)

    DP? Problem Description Figure 1 shows the Yang Hui Triangle. We number the row from top to bottom 0 ...

随机推荐

  1. python Logger模块单例模式

    前言 提前祝大家过个好年 最近忙于项目,今天抽出点时间写写Blog谈谈昨天遇到的问题 项目最近要收尾了,想把Logger规整一下,因为很多地方都有用到 Python的Logger模块是Python自带 ...

  2. VRay for SketchUp渲染图黑原因及解决方案

    很多人都遇到用Vray for SketchUp云渲染的时候,渲染出来的图片是全黑或者是局部是黑色, 这是什么原因呢? 1.有一种情况是,SketchUp的文件储存机制和其他的软件有些不同,它是把模型 ...

  3. oracle坚决不挂2(SQLPLUS基础命令)

    继续复习!!SQLplus基础命令,其实这个应该是第一个要复习的.因为基础,你懂得..要想学会跑,你先得知道该怎么走吧. win+R 输入cmd ,我们开始启动SQLplus sqlplus user ...

  4. Linux学习笔记 | 常见错误之VMware启动linux后一直黑屏

    方法1: 宿主机(windows)管理员模式运行cmd 输入netsh winsock reset 然后重启电脑 netsh winsock reset命令,作用是重置 Winsock 目录.如果一台 ...

  5. 通用寄存器_MOV_ADD_SUB_AND_OR_NOT

    通用寄存器 MOV指令 注意:目标操作数与操作数宽度必须一样 MOV 目标操作数,源操作数 作用:拷贝源操作数到目标操作数 1.源操作数可以是立即数.通用寄存器.段寄存器.或者内存单元. 2.目标操作 ...

  6. LeetCode404.左叶子之和

    题目 法一.广度优先搜索 1 class Solution { 2 public: 3 int sumOfLeftLeaves(TreeNode* root) { 4 if(root == NULL) ...

  7. CTFHub - Web(二)

    目录遍历: 法一: 依次查看目录即可: 法二: 利用脚本:  #!/usr/bin/python3  # -*- coding: utf-8 -*-  # --author:valecalida-- ...

  8. ctfhub技能树—彩蛋

    彩蛋题建议大家首先自己动手去找一找 做 好 准 备 后 再 看 下 文 !           1.首页 使用域名查询工具查询子域名 2.公众号 此题关注ctfhub公众号即可拿到,不过多赘述. 3. ...

  9. postgresql插件安装

    postgresql安装包自带插件安装: 1.编译安装插件 # root用户 # postgresql安装过程省略 # 进入解压包的contrib目录 cd postgresql-10.6/contr ...

  10. Junit测试和反射

    Junit单元测试 测试分类 黑盒测试:不需要写代码,给输入值,看程序能否得到输出期望值. 白盒测试:需要些代码,关注程序具体的执行流程. Junit的使用 步骤 定义一个测试类(测试用例). 定义测 ...