【排列组合】 组合(lucas定理)

题目描述
给出组合数 C(n,m) 表示从 n 个元素中选出 m 个元素的方案数。例如 C(5,2)=10,C(4,2)=6。可是当 n,m 比较大的时候,C(n,m) 很大。于是 xiaobo 希望你输出C(n,m)modp 的值。
输入
输入数据第一行是一个正整数 T,表示数据组数;
接下来是 T 组数据,每组数据有 3 个正整数 n,m,p。
对于所有数据,T≤100,1≤m≤n≤109,m≤104,m<p<109,p 是素数。
输出
对于每组数据,输出一个正整数,表示 C(n,m)modp 的结果。
样例输入
2
5 2 3
5 2 61
样例输出
1
10

思路:对于卢卡斯定理的运用,直接套板子。但是我虽然看懂了定理的式子,但对于代码的实现也不是很透彻,下次碰到也直接套板子吧

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll fastpow(ll n, ll k, ll p)
{
   ll ans=1,cnt=n;
   while(k)
   {
       if(k&1)ans=(ans*cnt)%p;
       cnt=(cnt*cnt)%p;
       k>>=1;
   }
   return ans;
}
ll comb(ll n, ll m, ll p)//求小数字的C(n,m)
{
    if(m>n)return 0;//不符合数学要求
    if(m>n-m)m=n-m;//简化计算
    if(m==0)return 1;//m==0的特殊情况
    ll up=1,down=1;//分别为分子分母
    //勤勤恳恳老实算阶乘
    for(int i = 1; i <= m; i++)
    {
        up=(up*(n-i+1))%p;//分子从大到小
        down=(down*i)%p;//分母从小到大
    }
    //没懂这里为什么不是up/down
    //据说这里的fastpow是down的乘法逆元
    //根据费马小引理+欧拉定理,a的乘法逆元是a^(p-2)
    return up*fastpow(down,p-2,p)%p;
}
ll lucas(ll n, ll m, ll p)//LUCAS定理
{
    if(m==0)return 1;
    //没懂这里为什么不是lucas*lucas
    return comb(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}
int main()
{
    ll n,m,p,T;
    cin>>T;
    while(T--)
    {
        cin>>n>>m>>p;
        cout<<lucas(n,m,p)<<endl;
    }
    return 0;
}

补充:看了大佬的教学视频彻底理解了这个题的代码。
当初关于逆元的疑问都在这篇博客了:https://blog.csdn.net/qq_44661312/article/details/103324485

卢卡斯定理:c(a,b) ≡ c(a%p,b%p) * c(a/p,b/p) (mod p)。
这个定理的应用就是当ab数值特别大的时候,用小数值p来降低大数字a\b,因此代码第36行就不是Lucas*Lucas,因为这个函数只是用来减小ab的,comb才是用来算最终的答案的函数。
(2019.12.6)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值