通过费马小定理求组合数(防止越界,因为做除法之前不能够取余)

本文介绍如何利用费马小定理简化高次幂运算,并给出了一种求解大规模组合数C(n,m)的方法,避免了直接计算阶乘导致的数据溢出问题。此外,还提供了一个具体的编程实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:http://blog.youkuaiyun.com/xuezhongfenfei/article/details/10100651

费马小定理:a^(p-1) ≡1(mod p)a和p是互质的(p为质数)。 那么对于任意的求a^m%p则可以转换为a^(m%(p-1))%p,可以使复杂度很高的求此方的进行化简

欧拉函数上的应用就是:  a,m互质,a^φ(m)≡1(mod m)//(φ(m)为欧拉函数,表示的是m之前与m互质的正整数的个数,而如果m为质数的话则m-1就为m的欧拉函数值,这又到了费马小定理)

求排列组合中的C(n,m),如果n和m都很大,而且取得的值是要模上mod,那么如果仍然按照n!/(m!*(n-m)!)算的话肯定就会爆,如果边去摸边算的话结果就会不对,那么应该怎么算呢?

转换为n!*(m!)^(md-2)*((n-m)!^mod-2);mod-2也可以为mod的欧拉函数值-1,这里暂且设mod为质数

因为求的是n!/(m!*(n-m)!),则可以转换为n!*(m!)^(-1)*(n-m)!^-1,

n!*m!^(-1)*(n-m)!^(-1) *m!^(mod-1) *(n-m)!^(mod-1),因为后两项都为1,所以乘后结果不变,然后就转换为了上面的式子,这样就不用担心除法的时候因为取模的问题了!


有一道例题,hdu4869 ,第一场多校第九题。记录一下。

/***********************************************\
 |Author: Messyidea
 |Created Time: 2014-7-22 13:48:33
 |File Name: b.cpp
 |Description: 
\***********************************************/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <string>
#include <cstring>
#include <algorithm>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <queue>
#include <stack>
#define L(rt) (rt<<1)
#define R(rt) (rt<<1|1)
#define mset(l,n) memset(l,n,sizeof(l))
#define rep(i,n) for(int i=0;i<n;++i)
#define maxx(a) memset(a, 0x3f, sizeof(a))
#define zero(a) memset(a, 0, sizeof(a))
#define srep(i,n) for(int i = 1;i <= n;i ++)
#define MP make_pair
const int inf=0x3f3f3f3f ;
const double eps=1e-8 ;
const double pi=acos (-1.0);
typedef long long ll;
#define mod 1000000009
#define LL long long 
using namespace std;
int n,m;
ll f[100005];
LL extend_gcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    LL gcd=extend_gcd(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-a/b*x;
    return gcd;
}
LL Get_Inverse(LL num)
{
    LL x,y;
    extend_gcd(num,mod,x,y);
    return (x%mod+mod)%mod;
}
LL Combine(LL n,LL m)//计算组合数C(n,m)
{
    LL t1=1,t2=1;
    for(LL i=n;i>m;i--)
    {
        t1=(t1*i)%mod;
        t2=(t2*(i-m))%mod;
    }
    return t1*Get_Inverse(t2)%mod;
}

void pre(){
    int i;
    f[0] = 1;
    for(int i=1;i<100005;++i){
        f[i] = (f[i-1]*i)%mod;
    }
}

int ma[100005];
int da[100005];
int l,r;
ll quickmod(ll x,ll y){
    ll res = 1;
    while(y>0){
        if(y&1) res = res * x % mod;
        x = x * x % mod;
        y>>=1;
    }
    return res;
}
void solve(){
    l = r = m;
    int num = 0;
    int p;
    if(m % 2 == 0) {
        p = 0;
    } else {
        p = 1;
    }
    rep(i,n){
        int tl = l,tr = r;
        if(ma[i] % 2 == 1) p = p ^ 1;
        num = 0;
        if(m - tl >= ma[i]) da[num ++] = tl + ma[i];
        if(tl >= ma[i]) da[num++] = tl - ma[i];
        if(m - tl < ma[i]){
            da[num ++] = m-(ma[i] - (m-tl));
        }
        if(tl < ma[i]) {
            da[num ++] = ma[i] - tl;
        }
        if(m - tr >= ma[i]) da[num ++] = tr + ma[i];
        if(tr >= ma[i]) da[num++] = tr - ma[i];
        if(m - tr < ma[i]){
            da[num ++] = m-(ma[i] - (m-tr));
        }
        if(tr < ma[i]) {
            da[num ++] = ma[i] - tr;
        }
        if(tr >= ma[i] && tl <= ma[i]){
            if(p == 1) da[num ++] = 1;
            else da[num ++] = 0;
        }
        if(tr + ma[i] >= m && tl + ma[i] <= m ){
            if(p == 1){
                if(m % 2 == 0) da[num++] = m-1;
                else da[num ++] = m;
            } else {
                if(m % 2 == 0) da[num ++] = m;
                else da[num ++] = m-1;
            }
        }
        sort(da,da+num);
        //rep(i,num) cout<<da[i]<<" ";cout<<endl;
        tl = da[0];tr = da[num-1];
        l = tl;r = tr;
    }
    LL ans = 0;
    ans = 0;
    LL tp = Combine(m,l);
    //cout<<l<<" "<<r<<endl;
    for(int i = l;i <= r;i += 2){
        ans += f[m]*quickmod(f[m-i],mod-2)%mod*quickmod(f[i],mod-2)%mod;
        ans %= mod;
    }
    printf("%lld\n",ans);
}
int main() {
    //freopen("input.txt","r",stdin); 
    pre();
    while(~scanf("%d%d",&n,&m)){
        rep(i,n) scanf("%d",&ma[i]);
        solve();
    }
    return 0;
}


帮我调试这份代码并把错误点标记出来#include<bits/stdc++.h> #define int long long using namespace std; const int MAXN = 2e5 + 5; const int MOD = 1e9 + 7; int N, M; int jc[MAXN], inv[MAXN], zys[100], cnt[100]; int qpow( int a, int b ){ int base = a, ans = 1; while( b > 0 ){ if( b & 1 ){ ans *= base; ans %= MOD; } base *= base; base %= MOD; b >>= 1; } return ans; } void jiec(){ jc[0] = jc[1] = inv[0] = inv[1] = 1;//初始化 for( int i = 2; i <= N; i ++ ){ jc[i] = 1LL * jc[i - 1] * i % MOD; // inv[i] = 1LL * ( MOD - MOD / i ) * inv[MOD % i] % MOD; } inv[0] = 1; inv[N] = qpow(jc[N], MOD - 2); // 费马小定理逆元 for (int i = N - 1; i >= 1; i--) { inv[i] = 1LL * inv[i + 1] * (i + 1) % MOD; } // for( int i = 1; i <= N; i ++ ) inv[i] = 1LL * inv[i - 1] * inv[i] % MOD; } int C( int n, int m ){ if( n < m ) return 0; return jc[n] * inv[m] % MOD * inv[n - m] % MOD; } signed main(){ cin >> N >> M; jiec(); int pos = 0; for( int i = 2; i * i <= M; i ++ ){ if( M % i == 0 ){ zys[++ pos] = i; cnt[pos] = 0; while( M % i == 0 ){ cnt[pos] ; M /= i; } } } if( M > 1 ){ zys[ pos] = M; cnt[pos] = 1; } int ans = 1; for( int i = 1; i <= pos; i ++ ){ ans = ans * C( N + cnt[i] - 1, cnt[i] ) % MOD; } cout << ans; return 0; }题面如下# AT_abc110_d [ABC110D] Factorization 题目描述 正整数 N , M N, M が与えられます。 a 1 × a 2 × . . . × a N = M a 1 ​ × a 2 ​ × ... × a N ​ = M となる正整数からなる長さ N N の数列 a a が何通りあるかを 10 9 + 7 10 9 +7 で割ったりをめてください。 ただし、数列 a ′ a ′ と a ′ ′ a ′′ が異なるとは、ある i i が存在して a i ′ ≠ a i ′ ′ a i ′ ​  = a i ′′ ​ であることをいいます。 输入格式 入力は以下の形式で標準入力から与えられる。 N N M M 输出格式 条件を満たす正整数からなる数列が何通りあるかを 10 9 + 7 10 9 + 7 で割ったりを出力せよ。 输入输出样例 #1 输入 #1 2 6 输出 #1 4 输入输出样例 #2 输入 #2 3 12 输出 #2 18 输入输出样例 #3 输入 #3 100000 1000000000 输出 #3 957870001 说明/提示 制約 入力はすべて整数である 1 ≤ N ≤ 10 5 1 ≤ N ≤ 10 5 1 ≤ M ≤ 10 9 1 ≤ M ≤ 10 9 Sample Explanation 1 { a 1 , a 2 } = { 1 , 6 } , { 2 , 3 } , { 3 , 2 } , { 6 , 1 } {a 1 ​ , a 2 ​ } = {1, 6}, {2, 3}, {3, 2}, {6, 1} の 4 4 通りの数列が条件を満たします。要改变变量名和结构
07-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值