hdu 3037 Skiing

组合数学-大组合数取模(Lucas定理)

题意:

将不超过m颗的相同的豆子放在n棵不同的树上,每棵树可以为空,求方案数mod p

(1 <= n, m <= 1000000000, 1 < p < 100000,p是质数)

分析:

可以理解为有m颗豆子,在n棵树上放k颗,然后再加一棵树,放m-k颗,于是变成了m颗相同的豆子放在n+1棵不同树上的方案数。

也就是求a[1]+a[2]+a[3]+......+a[n+1]=m,(a[i]>=0)的方案数,但是这种情况并不好计算,我们可以让每一份都加上1,令b[i]=a[i]+1>=1, 则b[1]+b[2]+b[3]+......b[n+1]=m+n+1,

可以用插板法了,m+n+1个元素有m+n个空,分成n+1份就是插n个板子,所以答案就是C(n+m,m) %p


数论Lucas定理: 用来求 c(n,m) mod p的值,p是素数(从n取m组合,模上p)。
描述为:
Lucas(n,m,p)=cm(n%p,m%p)* Lucas(n/p,m/p,p)
Lucas(x,0,p)=1;
cm(a,b)=a! * (b!*(a-b)!)^(p-2) mod p
也= (a!/(a-b)!) * (b!)^(p-2)) mod p
这里,其实就是直接求 (a!/(a-b)!) / (b!) mod p
由于 (a/b) mod p = a * b^(p-2) mod p

代码:

#include <cstdio>  
#include <cstdlib>  
#include <cstring>  
#include <algorithm>  
using namespace std;
  
const int MAX = 100005;  
typedef long long ll;  
  
ll f[MAX];  

ll mul(ll x, ll y, ll p)  
{  
    ll res = 0;  
    while(y)  
    {  
        if(y&1) res = (res+x) % p;  
        x = (x<<1) % p;  
        y >>= 1;  
    }  
    return res;  
}  
void exgcd(ll a, ll b, ll &d, ll &x, ll &y)  
{  
    if (!b)  
    {  
        d = a;  
        x = 1;  
        y = 0;  
    }  
    else  
    {  
        exgcd(b, a%b, d, y, x);  
        y -= a/b * x;  
    }  
}  
void initp(int p)  
{  
    f[0] = f[1] = 1;  
    for(int i = 2; i < p; i++)
        f[i] = (f[i-1]*i) % p;  
}  
  
ll comb(ll n, ll m, ll p)  
{  
    if(m > n) return 0;  
    ll ans = f[n];  
    ll g, x, y;  
    exgcd(f[m], p, g, x, y);  
    ans = mul(ans, (x%p)%p+p, p);  
    exgcd(f[n-m], p, g, x, y);  
    ans = mul(ans, (x%p)%p+p, p);  
    return ans;  
}  
  
//n取m,如果可能m>n请特判return 0;  
ll lucas(ll n, ll m, int p)  
{  
    ll ans = 1;  
    initp(p);  
    if(m > n) return 0;  
    while(n && m && ans)  
    {  
        ans = mul(comb(n%p, m%p, p), ans, p);  
        n /= p;  
        m /= p;  
    }  
    return ans;  
}  
void work()  
{  
    ll n, m, p, ans;  
    scanf("%I64d%I64d%I64d", &n, &m, &p);  
    ans = lucas(n + m, m, p);  
    printf("%I64d\n", ans);  
}  
int main()  
{  
    int T;  scanf("%d",&T);  
    while(T--)  
        work();  
    return 0;  
}  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值