数论篇7——组合数 & 卢卡斯定理(Lucas)

组合数

组合数就是高中排列组合的知识,求解组合数C(n,m),即从n个相同物品中取出m个的方案数。

求解方式

求解通式:$C^{m}_{n}=\dfrac {n!}{m!\left( n-m\right) !}$

性质1:$C^{m}_{n}=C_{n}^{n-m}$

性质2:$C^{m}_{n}=C^{m-1}_{n-1}-i+C^{m}_{n-1}$

打表递推

根据性质2:$C^{m}_{n}=C^{m-1}_{n-1}+C^{m}_{n-1}$

组合数算出来特别大,往往都会要求取余,这里取$P=1e9+7$。时间复杂度$O(n^2)$

const int P = 1e9 + 7;
#define N 1000
int comb[N][N];

int main() {
    for (int i = 0; i < N; i++) {
        comb[i][0] = comb[i][i] = 1;
        for (int j = 1; j < i; j++) {
            comb[i][j] = comb[i - 1][j] + comb[i - 1][j - 1];
            comb[i][j] %= P;
            //cout << comb[i][j] << endl;
        }
    }
}

逆元法

因为大部分题都有求余,可利用逆元的原理(没求余的题目,自己找一个比较大的素数作为P,也可以用逆元做)

线性递推求逆元

当$p$为质数时有$a^{-1}=(p-[p/a])\cdot (p\%a)^{-1}\%p$

求阶乘的逆元

根据通式:$C^{m}_{n}=\dfrac {n!}{m!\left( n-m\right) !}$,有$C^{m}_{n}=n!\cdot inv[m!] \cdot inv[(n-m)!]$

设 $finv(i)=inv(i\ !)$

则根据:$finv(i-1)=\frac{1}{\ (i-1)\ !}=\frac{1}{i\ !}\times i =finv(i)\times i$

有:$finv(i) = finv(i-1)\times inv(i)$

详见:数论篇4——逆元(数论倒数)

初始化时间复杂度$O(n)$,求$C^{m}_{n}$为$O(1)$

const int N = 200000;
const int P = (int)1e9 + 7;
int fact[N+5], Finv[N+5], inv[N+5];//fact是阶乘,Finv是阶乘的逆元
void init() {
    inv[1] = 1;
    //线性递推求逆元
    for (int i = 2; i <= N; i++) {
        inv[i] = (P - P / i) * 1ll * inv[P % i] % P;
    }
    fact[0] = Finv[0] = 1;
    for (int i = 1; i < N; i++) {
        fact[i] = fact[i - 1] * 1ll * i % P;//求阶乘
        Finv[i] = Finv[i - 1] * 1ll * inv[i] % P;//求阶乘的逆元
    }
}
int C(int n, int m) {//comb(n, m)就是C(n, m)
    if (m < 0 || m > n) return 0;
    return fact[n] * 1ll * Finv[n - m] % P * Finv[m] % P;
}

卢卡斯定理

现在有了新问题,如果$n$和$m$非常大,$p$为素数,比如求$C_n^m \% p \ ,\ n\leqslant 10^{18},m\leqslant 10^{18},p\leqslant 10^{9}$

$C_n^m\ \%\ p  =  C(n / p, m / p) * C(n\ \%\ p, m\ \%\ p)\ \%\  p$

或者写成这样更准确$Lucas(n,m)\ \%\ p=Lucas(n/p,m/p)*C(n\ \%\ p,m\ \%\ p)\ \%\ p$

证明请看此 lucas_百度百科,没仔细看证明,所以对不对我也不知道。

写成递归,代码就这么短:

LL Lucas(LL n, LL m, int p){
         return m ? Lucas(n/p, m/p, p) * C(n%p, m%p, p) % p : 1;
}

具体C的实现要看情况。

P较小时,打表

typedef long long ll;
const int N = 1e5 ;
const int P = 99991;//取一个小于N的素数
ll fact[P + 5], inv[P + 5], Finv[P + 5];//阶乘打表

void init() {
    inv[1] = 1;
    //线性递推求逆元
    for (int i = 2; i <= P; i++) {
        inv[i] = (P - P / i) * 1ll * inv[P % i] % P;
    }
    fact[0] = Finv[0] = 1;
    for (int i = 1; i < P; i++) {
        fact[i] = fact[i - 1] * 1ll * i % P;//求阶乘
        Finv[i] = Finv[i - 1] * 1ll * inv[i] % P;//求阶乘的逆元
    }
}

ll C(ll n, ll m){//组合数C(n, m) % p
    if (m > n)return 0;
    return fact[n] * Finv[n - m] % P * Finv[m] % P;
}
ll Lucas(ll n, ll m){
    return m ? C(n % P, m % P) * Lucas(n / P, m / P) % P : 1;
}

P较大时,没法打表,用快速幂算逆元

typedef long long ll;

const int N = 1e9 ;
const int P = 1e8 + 7;

ll quickPower(ll a, ll b) {
    ll res = 1;
    a %= P;
    while (b) {
        if (b & 1)res = (res % P) * (a % P) % P;
        a = (a % P) * (a % P) % P;
        b >>= 1;
    }
    return res;
}
ll inv(ll x) {//x关于p的逆元,p为素数
    return quickPower(x, P - 2);
}
ll C(ll n, ll m) {
    if (m > n)return 0;
    ll up = 1, down = 1;//分子分母;
    for (int i = n - m + 1; i <= n; i++)
        up = up * i % P;
    for (int i = 1; i <= m; i++)
        down = down * i % P;
    return up * inv(down) % P;
}
ll Lucas(ll n, ll m) {
    if (m == 0)return 1;
    return C(n % P, m % P) * Lucas(n / P, m / P) % P;
}

原文地址:https://www.cnblogs.com/czc1999/p/11745171.html

时间: 2024-10-11 08:06:12

数论篇7——组合数 & 卢卡斯定理(Lucas)的相关文章

ACM数论之旅10---大组合数-卢卡斯定理(在下卢卡斯,你是我的Master吗?(。-`ω&#180;-) )

记得前几章的组合数吧 我们学了O(n^2)的做法,加上逆元,我们又会了O(n)的做法 现在来了新问题,如果n和m很大呢, 比如求C(n, m) % p  , n<=1e18,m<=1e18,p<=1e5 看到没有,n和m这么大,但是p却很小,我们要利用这个p (数论就是这么无聊的东西,我要是让n=1e100,m=1e100,p=1e100你有本事给我算啊(°□°),还不是一样算不出来) 然后,我们著名的卢卡斯(Lucas)在人群中站了出来(`?д?´)说:“让老子来教你这题” 卢卡斯说:

卢卡斯定理 Lucas (p为素数)

证明摘自:(我网上唯一看得懂的证明) https://blog.csdn.net/alan_cty/article/details/54318369 结论:(显然递归实现)lucas(n,m)=lucas(n/p,m/p)*C(n%p,m%p) 将n,m很大的数压成求两个小于p的组合数的乘积 数学上的卢卡斯定理两种形式:(n,m用p进制表示) 上代码: //打表 void init(ll x){ rec[0]=1; For(i,1,x)mulmod(rec[i],rec[0]*i); } //逆

洛谷.3807.[模板]卢卡斯定理(Lucas)

题目链接 Lucas定理 日常水题...sublime和C++字体死活不同步怎么办... //想错int范围了...不要被longlong坑 //这个范围现算阶乘比预处理快得多 #include <cstdio> typedef long long LL; const int N=1e5+5; LL n,m,p;//,fac[N+3]; LL FP(LL x,LL k,LL p) { LL t=1; for(; k; k>>=1,x=x*x%p) if(k&1) t=t*x

# 数论-组合数+lacus定理

目录 数论-组合数+lacus定理 组合数计算 lacus定理-大组合数取模 数论-组合数+lacus定理 组合数计算 为避免爆long long,\(20!\)就达到了long long 的范围,采用边乘边除的思想 ll C(ll n,ll m){ if(n<m)return 0; ll ans=1; for(ll i=1;i<=m;i++) ans=ans*(n-m+i)/i; return ans; } lacus定理-大组合数取模 卢卡斯定理:C(n,m)%p=C(n/p,m/p)*C

卢卡斯定理

卢卡斯定理:解决一类组合数取模问题 A.B是非负整数,p是质数.AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]. 则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0])  modp同余 即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 这个是单独处理n!的情况,当然C(n,m)就是n!/(m!*(n-m)!),每一个阶乘都用上面的方法处理的话,就是Luc

【BZOJ 1272】 1272: [BeiJingWc2008]Gate Of Babylon (容斥原理+卢卡斯定理)

1272: [BeiJingWc2008]Gate Of Babylon Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 254  Solved: 120 Description Input Output Sample Input Sample Output 12 HINT Source [分析] T很小,跟以前的某一题很像啊,就是容斥. 枚举不符合的(超过限制的),2^t,然后就是算 n种无限多的东东中选m个. 经典的组合数题,$C_{n+m-1

HDU 5794 A Simple Chess(卢卡斯定理 + 容斥原理)

传送门 A Simple Chess Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 667    Accepted Submission(s): 168 Problem Description There is a n×m board, a chess want to go to the position (n,m) from the

洛谷 P3807 【模板】卢卡斯定理

题目背景 这是一道模板题. 题目描述 给定n,m,p(1\le n,m,p\le 10^51≤n,m,p≤105) 求 C_{n+m}^{m}\ mod\ pCn+mm? mod p 保证P为prime C表示组合数. 一个测试点内包含多组数据. 输入输出格式 输入格式: 第一行一个整数T(T\le 10T≤10),表示数据组数 第二行开始共T行,每行三个数n m p,意义如上 输出格式: 共T行,每行一个整数表示答案. 输入输出样例 输入样例#1: 复制 2 1 2 5 2 1 5 输出样例#

卢卡斯定理的模板以及应用

定义: Lucas定理是用来求 C(n,m) MOD p,p为素数的值.Lucas定理:我们令n=sp+q,m=tp+r.(q,r≤p) 那么:(在编程时你只要继续对 调用 Lucas 定理即可.代码可以递归的去完成这个过程,其中递归终点为 t=0 :时间复杂度 O(logp(n)?p):) 主要解决当 n,m 比较大的时候,而 p 比较小的时候 <1e6 ,那么我们就可以借助 卢卡斯定理来解决这个问题: 模板: #include <iostream> #include <cstd