HDU 5446 Unknown Treasure(Lucas定理+中国剩余定理)

本文介绍了一种高效计算大数组合数C(n,m)%M的方法,利用Lucas定理将问题分解,并通过中国剩余定理求解模方程组,提供了完整的C++代码实现。

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

Description
求C(n,m)%M
Input
第一行一整数T表示用例组数,每组用例输入三个整数n,m,k,之后k个不同的素数p1~pk表示M=p1*p2*…*pk
(T<=20,1<=m<=n<=10^18,1<=k<=10,M<=10^18,pi<=10^5)
Output
对于每组用例,输出C(n,m)%M
Sample Input
1
9 5 2
3 5
Sample Output
6
Solution
设x=C(n,m),因n和mm都比较大故可以由lucas定理得到C(n,m)%pi(1<=i<=k)的值ai,进而得到一个模方程组x mod pi=ai,因模数互素由中国剩余定理可以得到该方程组的通解,即x=t*M+x0(0<=x0< M),x0即为答案
Code

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
#define maxn 111111
ll rev[maxn];
ll power[maxn]; 
ll mod_pow(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}
void init(ll n,ll p)//预处理出n的阶乘及其阶乘的逆元 
{
    power[0]=1;
    for(int i=1;i<n;i++)
        power[i]=power[i-1]%p*i%p;
    rev[1]=1;
    for(ll i=0;i<n;i++) 
        rev[i]=mod_pow(power[i],p-2,p);
}
ll C(ll n,ll m,ll p)//求组合数 
{
    if(m>n||m<0||n<0)return 0;
    if(m==0||m==n) return 1;
    return power[n]*rev[m]%p*rev[n-m]%p;
}
//n,m较大时需要用到lucas定理
ll lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m)
    {
        ll a=n%p,b=m%p;
        if(a<b) return 0;
        ans=ans*C(a,b,p)%p;
        n/=p;
        m/=p;
    }
    return ans;
} 
ll mul(ll a,ll b,ll p)
{
    if(b<0)b=(b%p+p)%p;
    a%=p;
    if(a<b)swap(a,b);
    ll ans=0;
    while(b)
    {
        if(b&1)ans=(ans+a)%p;
        a=(a+a)%p;
        b>>=1;
    }
    return ans;
}
ll extend_gcd(ll a,ll b,ll &x,ll &y)
{ 
    ll d=a;
    if(b!=0)
    {
        d=extend_gcd(b,a%b,y,x);
        y-=(a/b)*x;
    }
    else 
    {
        x=1;
        y=0;
    }
    return d;
}
ll CRT(ll a[],ll m[],int n)//a数组表示余数,m数组表示模数,n表示方程个数 
{
    ll M=1,Mi,ans=0;
    ll x,y,d;
    for(int i=0;i<n;i++) 
        M*=m[i];
    for(int i=0;i<n;i++)
    {
        Mi=M/m[i];
        d=extend_gcd(Mi,m[i],x,y);
        ans=(ans+mul(mul(Mi,x,M),a[i],M))%M;
    }
    if(ans<0) ans+=M;
    return ans;
}
int T,k;
ll n,m,r[11],p[11];  
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d%d",&n,&m,&k);
        for(int i=0;i<k;i++)
        {
            scanf("%d",&p[i]);
            init(p[i],p[i]);
            r[i]=lucas(n,m,p[i]);
        }
        ll ans=CRT(r,p,k);
        printf("%I64d\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值