第三十二章 数论——组合数详解(1)

news2024/10/6 0:32:56

第三十二章 数论——组合数的多种求法

  • 一、数学基础
  • 二、组合数——递推公式
    • 1、题目
    • 2、思路
    • 3、代码
  • 三、组合数——快速幂
    • 1、问题:
    • 2、分析

一、数学基础

组合数来自于高中排列组合的知识:

我们从 a a a个小球中随机一次性取出 b b b个,所有的取法记作: C a b C_a^b Cab

这个 C a b C_a^b Cab怎么算呢?

C a b = a ! b ! ( a − b ) ! C_a^b=\frac{a!}{b!(a-b)!} Cab=b!(ab)!a!

二、组合数——递推公式

1、题目

在这里插入图片描述

2、思路

这道题如果我们强行用公式进行计算的话,我们一定是会超时的,为什么呢?

如果按照公式,我们需要算一个数的阶乘,那么数据范围是2000,这样的话,我们算阶乘所需的最大次数就是2000。

而我们是多组询问,这样的话,我们所需的时间就是100000*2000

这个次数就非常多了,因此基本上大概率超时了。

所以我们换一个方式:

我们在高中阶段曾经学过这样一个公式:
C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b+C_{a-1}^{b-1} Cab=Ca1b+Ca1b1

大家可以利用刚才的定义公式进行验证,当然这个也可以理解为我们后面的 01 01 01背包问题的状态转移方程。

这样做的好处是什么呢?

我们发现,我们这个递推公式是从小推大。
因此,我们在算出 c [ a ] [ b ] c[a][b] c[a][b]的时候,它前面的所有情况我们就都算出来了。

但是如果我们如果是采用刚刚的定义的话,我们每次查询都要计算一次。

而现在的话,我们可以通过一次计算预处理出来所有的情况。后续的查询只需要查表即可。这个时间复杂度就大大减少了。

3、代码

#include<iostream>
using namespace std;
const int N=2010;
const int mod=1e9+7;
int c[N][N];
void init()
{
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<=i;j++)
        {
            if(!j)c[i][j]=1;
            else c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
}
int main()
{
    int n;
    init();
    cin>>n;
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",c[a][b]);
    }
    return 0;
}

我们这里需要注意的是,我们的 c i 0 = 1 c_i^0=1 ci0=1

很多同学会担心下标出现-1的情况,但其实经过我们的特判,它只会运行到:if(!j)c[0[0]=1;这一行,不会进行后续的代码。因此,不会出现越界的情况。

如果我们打印一下我们的预处理的话,我们发现这就是我们很熟悉的C语言练习题:杨辉三角
在这里插入图片描述

三、组合数——快速幂

1、问题:

在这里插入图片描述

2、分析

这道题的关键在于我们 a a a b b b的范围是非常大的,所以我们很难通过开辟一个二维数组去预处理,不仅空间上很难找到这么大的一块空间,时间上也会出现很多多余的计算。

那么我们既然无法直接预处理最终的结果,我们可以根据定义预处理中间的过程。

根据我们的定义:

C a b = a ! b ! ( a − b ) ! C_a^b=\frac{a!}{b!(a-b)!} Cab=b!(ab)!a!

我们可以去预处理出所有的阶乘,然后根据定义运算的时候直接查表。

但是这里有一个问题,就是说我们的最终结果是对 1 e 9 + 7 1e9+7 1e9+7取模的结果。

我们预处理的时候,为了避免溢出,我们存储的肯定是每个阶乘取模之后的结果。

所以我们计算的结果是这样的:

C a b = a ! % m b ! % m ( a − b ) ! % m C_a^b=\frac{a!\%m}{b!\%m(a-b)!\%m} Cab=b!%m(ab)!%ma!%m

但是根据我们的模运算的法则:

a b % m ≠ a % m b % m \frac{a}{b}\%m\neq \frac{a\%m}{b\%m} ba%m=b%ma%m

因此,由于除法的出现,我们是无法正确计算出答案的。

那怎么办呢?

我们之前介绍过一个很重要的概念:乘法逆元

现在来回顾一下:

如果符合下面的同余式:

a b ≡ a ∗ x ( m o d   c ) \frac{a}{b}\equiv a*x(mod\ c) baax(mod c)

那么我们就称 x x x b b b m m m的乘法逆元,记作: b − 1 b^{-1} b1

这个式子其实并不好求逆元。

我们在之前的文章中还通过推导,发现上述的表达式还等价于:

b ∗ b − 1 ≡ 1 m o d ( c ) b*b^{-1}\equiv 1 mod(c) bb11mod(c)

如果 c c c是质数的话,我们可以使用费马小定理求解。
如果 c c c不是质数的话,我们可以使用扩展欧几里得算法求解。

这道题中我们的 1 e 9 + 7 1e9+7 1e9+7是质数,所以我们可以使用费马小定理

我们先回顾一下费马小定理的式子:

b p − 1 ≡ 1 m o d ( p ) b^{p-1}\equiv 1mod(p) bp11mod(p)

b ∗ b p − 2 ≡ 1 m o d ( p ) b*b^{p-2}\equiv 1mod(p) bbp21mod(p)

因此我们的乘法逆元: b − 1 = b p − 2 b^{-1}=b^{p-2} b1=bp2

而这个结果我们可以使用快速幂求解。

我们现在思考一下,我们枚举的 i i i的逆元一定存在吗?

答案是一定的。

因为 1 e 9 + 7 1e9+7 1e9+7是质数,所以它和1到 1 e 9 + 6 1e9+6 1e9+6都是互质的。

g c d ( i , 1 e 9 + 7 ) = 1 gcd(i,1e9+7)=1 gcd(i,1e9+7)=1

根据裴蜀定理:

我们先令 m = 1 e 9 + 7 m=1e9+7 m=1e9+7

必有 x i + y m = 1 xi+ym=1 xi+ym=1

x i = − y m + 1 xi=-ym+1 xi=ym+1

这个式子可以改写成:

x i ≡ 1 m o d ( m ) xi \equiv 1mod (m) xi1mod(m)

这个就是我们的逆元表达式, x x x就是我们要求的逆元,所以逆元必定存在

这里还有一个问题:

我们的阶乘满足: n ! = n ∗ ( n − 1 ) ! n!=n*(n-1)! n!=n(n1)!

那我们的逆元是否满足: n − 1 ! = n − 1 ∗ ( n − 1 ) − 1 ! n^{-1}!=n^{-1}*(n-1)^{-1}! n1!=n1(n1)1!呢?

答案是满足的。

n ∗ n − 1 ≡ 1 m o d ( m ) n*n^{-1}\equiv 1mod(m) nn11mod(m)

( n − 1 ) ∗ ( n − 1 ) − 1 ≡ 1 m o d ( m ) (n-1)*(n-1)^{-1}\equiv 1mod(m) (n1)(n1)11mod(m)

所以:
n ( n − 1 ) ∗ ( n − 1 ) − 1 n − 1 ≡ 1 m o d ( m ) n(n-1)*(n-1)^{-1}n^{-1}\equiv 1mod(m) n(n1)(n1)1n11mod(m)

即: ( n − 1 ) − 1 n − 1 (n-1)^{-1}n^{-1} (n1)1n1就是 n ( n − 1 ) n(n-1) n(n1)的阶乘。所以由此不断地乘在一起,即可验证我们的结论。

那么代码怎么写呢?

#include<iostream>
using namespace std;
typedef long long LL;
const int N=1e5+10;
const int mod=1e9+7;
LL fact[N],infact[N];
LL qmi(LL a,LL b,LL p)
{
    LL res=1;
    while(b)
    {
        if(b&1)res=res*a%p;
        a=a*a%p;
        b>>=1;
    }
    return res%p;
}
void init()
{
    fact[0]=1,infact[0]=1;
    for(int i=1;i<N;i++)
    {
        fact[i]=i*fact[i-1]%mod;
        infact[i]=infact[i-1]%mod*qmi(i,mod-2,mod)%mod;
    }
}
int main()
{
    init();
    int n;
    cin>>n;
    while(n--)
    {
        int a,b;
        scanf("%d%d",&a,&b);
        LL ans=fact[a]%mod*infact[b]%mod*infact[a-b]%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Numpy学习记录

numpy.ma.ravel 返回一个连续的扁平数组。 参考&#xff1a; numpy中的ravel()方法使用介绍https://blog.csdn.net/weixin_44025103/article/details/125062287 Python numpy.ravel函数方法的使用https://www.cjavapy.com/article/870/ numpy.meshgrid 二维坐标系中,X轴…

图片如何批量重命名?一步一步教会你

爱拍照的小伙伴应该都知道&#xff0c;无论是手机还是相机拍出来的照片&#xff0c;导入电脑后&#xff0c;它的默认名称都是一串长长的字符。不仅让桌面看起来杂乱无章&#xff0c;还会给我们在查找图片时带来诸多的不便。其实我们可以通过软件对这些图片进行批量重命名的&…

线性代数基础----矩阵

秩的理解: 举证的秩的理解: 特征值和特征向量 线性相关和线性无关 向量的拉伸和旋转: 特征值和特征向量: 特征值: 特征向量: 用来描述矩阵的信息 特征向量 和 特征值之间的关系 拳击:方向和力量 特征值和特征向量的物理意义: 表示相关的重要性 特征值和特征向量:进行提…

阳了在家没事干?教大家用python在家做一个万能看视频软件,绝对正经啦~

嗨害大家好鸭&#xff01;我是小熊猫~ 我前阵子不小心阳了&#xff0c;该说不说&#xff0c;真挺难受的 我在家真的就是纸巾热水不离手&#xff0c; 楼下水果店老板娘还说我年轻人身体怎么这么差… 哼我阳过之后我就锻炼去&#xff01;&#xff01;&#xff01; 效果展示 有…

如何删除掉设备和驱动器下百度网盘的图标

电脑安装百度网盘后&#xff0c;在设备和驱动器这里会有百度网盘的图标&#xff0c;如下图所示。 对有强迫症的人来说&#xff0c;这很难受&#xff0c;就一定要想办法删除掉该图标&#xff0c;那么具体怎么操作呢&#xff1f; 你如果在设备和驱动器下右击百度网盘&#xff0c…

12月小报|读小报,涨知识

本期知识小集的主要内容包括&#xff1a;• Flutter桥调用请注意结果反馈• Flutter await代码带来的潜在并发• Flutter FPS 高不代表一定流畅• Flutter新渲染引擎impeller尝鲜Flutter桥调用请注意结果反馈通过桥来拓展Flutter的能力&#xff0c;是非常通用的Flutter开发场景…

Weda创建视图表格

这边我们先创建一个数据集。 在更多操作里面分布这个数据集。 点击编辑&#xff0c;选择视图配置。 在下面这个界面&#xff0c;新建视图。 在操作里面&#xff0c;点击更多&#xff0c;发布这个视图。 我们点击编辑设置视图的第一行&#xff08;表列设置&#xff09;。 点击…

马上又是新的一年了 “跨年倒计时”送给大家

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;跨年倒计时 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪发光的…

pytorch 深度学习

第二章 回归问题 即使是最简单的线性回归,由于观测误差的存在,也不可能找到一个满足所有样本的函数。因此,我们退而求其次,寻找到一个满足大部分样本点的直线。那么如何衡量这个呢,我们可以求出所有样本点真实值和预测值的误差,满足总误差最小的就是最好的。 连续值…

年关在即,源站安全如何保障?|ScanV(云监测)重保哨兵值守

2022年&#xff0c;全球重大网络安全事件频发&#xff0c;大规模数据泄露、漏洞利用事件、勒索软件攻击等网络犯罪威胁持续上升。网络安全形势日趋严峻&#xff0c;对政府、党政机关、央企国企业务安全造成严重威胁。据知道创宇云防御2022年截至目前的数据统计&#xff0c;平均…

如何去掉任务栏的英伟达图标,并阻止英伟达服务自启动

进入服务&#xff0c;找到NVIDIA的两个服务项&#xff0c;右键属性&#xff0c;停止服务&#xff0c;英伟达图标自动消失。 再把启动方式改为手动&#xff0c;下次就不会开机自启。

WebGL

1、WebGL介绍 1.1 WebGL不足 效果较差&#xff1a;较于桌面开发API&#xff1a;Direct3D、OpenGL、UE、Unity。 开发成本&#xff1a;熟悉并掌握一定的数据知识&#xff0c;例如&#xff1a;线性代数。 硬件要求&#xff1a;开发及部署系统硬件要求较高&#xff0c;尤其GPU…

炒股经验总结

判断大盘调整到后期 高位补跌&#xff0c;大盘出现大阴线 板块炒作到尾声 板块龙头开始回调&#xff0c;炒作炒到边角料 良好的量价关系 放量突破&#xff0c;缩量回踩 跌破前低3天收不回来止损&#xff0c;无关涨跌&#xff0c;这是底线&#xff0c;以后涨了也不后悔&am…

正点原子IMX6ULL-Linux驱动开发

目录 第一期 第6讲 Ubuntu终端操作与Shell命令 第一期 第8讲 Ubuntu文件系统结构 第一期 第9讲 Ubuntu磁盘管理 第一期 第10讲 Ubuntu压缩与解压缩 第一期 第11讲 Ubuntu用户和用户组 第一期 第12讲 Ubuntu文件权限 第一期 第13讲Linux连接文件 第一期 第14讲 vim编辑器 第一期…

专利申请与专利转让有什么区别?

专利申请与专利转让有什么区别&#xff1f; 一、专利转让和专利申请的流程不同 专利转让的流程&#xff1a; (1)找到合适的转让途径; (2)专利转让人和专利受让人进行签署专利权转让合同; (3)填写专利转让相关文件&#xff0c;需要严格按照国家规定形式进行填写&#…

公司想要做自动化测试,那么自动化测试发展和价值回报有哪些?

很长一段时间&#xff0c;都在思考&#xff0c;怎么能通俗的看待自动化测试的收效 自动化测试到底能不能成为一种趋势&#xff1f; 自动化测试到底能不能形成一种规模&#xff1f; 自动化测试到底能不能成为我们的利器&#xff1f; 自动化测试到底能对我们的职业带来何种发…

为什么有的公司会禁用spring声明式事务

在之前我一直偏向于使用声明式事务&#xff0c;我一直觉得声明式事务比较好用。相比于编程式事务&#xff0c;使用声明式事务时只需要加上一个注解&#xff0c;spring就能够帮助我们完成所有的事务控制。反观编程式事务却需要我们自己去控制事务的提交和回滚&#xff0c;这种代…

【JavaSE】Clonable?关于深拷贝与浅拷贝那些事儿咱们一次聊明白

&#x1f481; 个人主页&#xff1a;黄小黄的博客主页 ❤️ 支持我&#xff1a;&#x1f44d; 点赞 &#x1f337; 收藏 &#x1f918;关注 &#x1f38f; 格言&#xff1a;All miracles start from sometime somewhere, make it right now. 本文来自专栏&#xff1a;JavaSE从入…

新年新气象,100行 Python 代码制作动态鞭炮

放鞭炮贺新春&#xff0c;在我国有两千多年历史。关于鞭炮的起源&#xff0c;有个有趣的传说。 西方山中有焉&#xff0c;长尺余&#xff0c;一足&#xff0c;性不畏人。犯之令人寒热&#xff0c;名曰年惊惮&#xff0c;后人遂象其形&#xff0c;以火药为之。——《神异经》 当…

IU8689 单声道145W/75W立体声D类音频功放IC产品介绍

概要 IU8689E是-款单声道可输出145W,立体声2*75W D类音频功率放大器&#xff0c;这款器件在顶层设计了散热焊盘&#xff0c;焊盘上连接散热器后在供电电压24V的情况下&#xff0c;最大可以输出2x75W的连续功率&#xff1b;通过主从模式的设置可以让IU8689E实现无限级联&#x…