hdu 5446 Unknown Treasure(lucas定理+中国剩余定理)

题目链接:

http://acm.hdu.edu.cn/showproblem.php?pid=5446

解题思路:

首先对于每个质数pi我们,我们可以利用Lucas定理求出 Cmn%pi 的值,Lucas定理如下:

Cmn%p=Cm/pn/pCm%pn%p%p

Lucas定理模板如下:

http://blog.csdn.net/piaocoder/article/details/48445215

然后我们可以利用中国剩余定理求取最后答案:

M=i=1kpi,Mi=M/pi

Cmn%M=i=1kCmn%piMiinv[Mi]

因为做乘法过程中可能会超long long ,所以我们要用快速乘法去处理,方便取模 

中国剩余定理:

我们假设同余方程组里所有的a[i]都等于1,并且所有的m[i]都互素。这样,答案就一定是x恒等于b(mod(m的所有和))。反之,对于一

个合数n,我们假设有n=ab(其中a和b互素)。那么如果x mod n的值确定,x mod a 和x mod b的值就都确定了。也就是说,我们有

(x mod n)<=>(x mod a,x mod b)这样一组对应关系。

换句话说,以合数n为模数来考虑与以a和b为模数来考虑时等价的。这个定理叫做中国剩余定理。通过对n进行分解,对于模合数的

情况只需要考虑模p的k次方(p为素数)的情况就可以了。其中如果n不能被任何一个完全平方数整除,那么问题就可以转化为模数

为素数的情况,从而变得容易求解,中国剩余定理不是一个算法,而是可以看成在思考算法时的一个提示。

例:f(x)恒等于0(mod n)<=>f(x)恒等于(mod p的k次方)(p的k次方|n)

AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;

typedef long long ll;
const int maxn = 20;
ll a[maxn];
ll mod[maxn] ;

ll mul(ll a,ll b,ll mod){
    ll ans = 0;
    while(b){
        if(b&1)
            ans = (ans+a)%mod;
        b >>= 1;
        a = (a+a)%mod;
    }
    return ans;
}

ll quick_mod(ll a,ll b,ll m){
    ll ans = 1;
    a %= m;
    while(b){
        if(b&1)
            ans = ans * a % m;
        b >>= 1;
        a = a * a % m;
    }
    return ans;
}

ll getC(ll n, ll m,int cur){
    ll p = mod[cur];
    if(m > n)
        return 0;
    if(m > n-m)
        m = n-m;
    ll ans = 1;
    for(ll i = 1; i <= m; i++){
        ll a = (n + i - m) % p;
        ll b = i % p;
        ans = mul(ans,mul(a,quick_mod(b,p-2,p),p),p);  //p为素数,i对p的逆元可以不用扩展欧几里得进行求解  re=i^(p-2)
    }
    return ans%p;
}

ll Lucas(ll n,ll k,int cur){
    ll p = mod[cur];
    if(k == 0)
        return 1%p;
    return mul(getC(n%p,k%p,cur),Lucas(n/p,k/p,cur),p);
}

void extend_Euclid(ll a, ll b, ll &x, ll &y){
    if(b == 0){
        x = 1;
        y = 0;
        return;
    }
    extend_Euclid(b,a%b,x,y);
    ll tmp = x;
    x = y;
    y = tmp - a / b * y;
}

ll solve(ll a[],ll m[],int k){
    ll M = 1;
    ll ans = 0;
    for(int i=0; i<k; i++)
        M *= mod[i];
    for(int i=0; i<k; i++){
        ll x,y,tmp;
        ll Mi = M / m[i];
        extend_Euclid(Mi, m[i], x, y);
        if(x < 0){
            x=-x;
            tmp = mul(Mi,x,M);
            tmp = mul(tmp,a[i],M);
            tmp = -tmp;
        }
        else {
            tmp = mul(Mi,x,M);
            tmp = mul(tmp,a[i],M);
        }
        ans = (ans + tmp) % M;
    }
    while(ans < 0)
        ans += M;
    return ans;
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        ll n,m;
        int k;
        scanf("%lld%lld%d",&n,&m,&k);
        for(int i = 0; i < k; i++)
            scanf("%lld",&mod[i]);
        for(int i = 0;i < k; i++)
            a[i] = Lucas(n,m,i)%mod[i];
        printf("%lld\n",solve(a,mod,k));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值