HDU - 5446 Unknown Treasure (数论部分定理的综合应用,lucas+CRT)

题目描述:

点击打开链接

题意无比简单,n,m,k求组合数C(n,m)%knum,其中knum由p1,p2,p3...pk,k个数相乘得到。这题麻烦的是超大的数据范围,所以我们要综合应用一些东西,由于knum是k个数的乘积,我们可以现将这k个数拆开计算,对于每一个C(n,m)%pi就是一个lucas,做完这些lucas之后我们可以得到一个线性同余方程,接下来就是中国剩余定理的过程了。相信做到这个题的时候,lucas和中国剩余定理应该都是会的,不会自信百度。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;

long long fact[100010];

long long qpow(long long x,long  long y,long long MOD)
{
    long long res=1;
    while(y)
    {
        if (y&1)
            res=(res*x)%MOD;
        //printf("--%lld\n",res);
            x=(x*x)%MOD;
        //printf("---%lld\n",x);
            y=y>>1;
    }
    return res;
}
long long mod_inverse(long long a,long long m)//求逆元。
{
    long long inv=qpow(a,m-2,m);
    //printf("%lld\n",inv);
    return inv;
}
long long mod_fact(long long n,long long p,long long &e)//阶乘取模。
{
    e=0;
    if (n==0) return 1;
    long long res=mod_fact(n/p,p,e);
    e+=n/p;
    if (n/p%2!=0) return res*(p-fact[n%p])%p;
    return res*fact[n%p]%p;
}
long long mod_comb(long long n,long long k,long long p)//组合数取模,Lucas
{
    if (n<0||k<0||n<k) return 0;
    long long e1,e2,e3;
    long long a1=mod_fact(n,p,e1);
    long long a2=mod_fact(k,p,e2);
    long long a3=mod_fact(n-k,p,e3);
    if (e1>e2+e3) return 0;
    return a1*mod_inverse(a2*a3%p,p)%p;
}
void fact_init(long long p)//阶乘打表
{
    fact[0]=1;
    for (int i=1;i<=p;i++)
        fact[i]=(fact[i-1]*i)%p;
}
long long multi_mod(long long x,long long y,long long c)
{
    x=(x%c+c)%c;
    y=(y%c+c)%c;
    long long ans=0;
    while(y){
        if(y&1)ans=ans+x;
        if(ans>=c)ans-=c;
        x=x+x;
        if(x>=c)x-=c;
        y>>=1;
    }
    return ans;
}
long long extgcd(long long a,long long b,long long &x,long long &y)//扩展欧几里德求逆元。
{
    long long d=a;
    if (b!=0)
    {
        d=extgcd(b,a%b,y,x);
        y-=(a/b)*x;
    }
    else
    {
        x=1;
        y=0;
    }
    return d;
}
long long CRT(long long n,long long a[],long long b[])
{
    long long M=1,d,y,x=0;
    for (int i=1;i<=n;i++) M=M*a[i];
    for (int i=1;i<=n;i++)
    {
        long long w=M/a[i];
        long long l=extgcd(a[i],w,d,y);
        x=(x+multi_mod(multi_mod(y,w,M),b[i],M));
    }
    return (x+M)%M;
}

long long n,m,k;

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&k);
        long long p;
        long long a[22],b[22];
        for (long long i=1;i<=k;i++)
        {
            scanf("%I64d",&p);
            a[i]=p;
            fact_init(p);
            b[i]=mod_comb(n,m,p);
        }
        long long ans=CRT(k,a,b);
        printf("%I64d\n",ans);
    }
    return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值