HDU 3944

    原来剑哥讲组合数取模的时候说过Lucas定理,但是不是很明白。总之C(n,k)%p = C(n/p,k/p)*C(n%p,k%p)%p。这里要注意如果k>n, C(n,k)=0。C(n,k)%p可以通过求逆元的方法计算。不过C(n,k)%p = n!/(k!*(n-k)!)%p = n!*((k!*(n-k)!)^(p-2))%p是更好的方法,编码简单一点,时间效率高一点。

    还要注意一点,这题比较恶心,每次都初始化阶乘数组肯定会超时。考虑到10000以内只有1229个素数,所以可以先打表。

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <memory.h>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <algorithm>
#include <iostream>
#include <sstream>

using namespace std;

// Lucas定理求解C(n,k)%p, p是素数
// C(n,k)%p = C(n%p,k%p)*C(n/p,k/p)%p
// C(n,k)%p = n!/(k!*(n-k)!)%p = n!*((k!*(n-k)!)^(p-2))%p
// pow_mod(a,b,p)求a^b%p的值, 快速幂取模算法
// com_mod(n,k,p)求C(n,k)%p的值
// lucas(n,k,p)使用lucas定理求C(n,k)%p的值

int pow_mod(int a, int b, int p) {
    int r = 1, t = a;
    while (b) {
        if (b & 1) r = r * t % p;
        t = t * t % p;
        b >>= 1;
    }
    return r;
}

int com_mod(int n, int k, int p, int *fac) {
    int a, b;
    if (k > n) return 0;
    a = fac[n];
    b = fac[n-k] * fac[k] % p;
    return a * pow_mod(b, p-2, p) % p;
}

int lucas(int n, int k, int p, int *fac) {
    int r = 1, a, b;
    while (n && k && r) {
        a = n % p;
        b = k % p;
        n /= p;
        k /= p;
        
        r = r * com_mod(a, b, p, fac) % p;
    }
    return r;
}

int pri[1250], cnt;
int fac[1250][10000];
bool not_prime[10005];

void init() {
    int i, j, p;
    cnt = 0;
    for (i = 2; i < 10000; i++) {
        if (not_prime[i]) continue;
        pri[cnt++] = i;
        for (j = i + i; j < 10000; j += i)
            not_prime[j] = 1;
    }
    for (i = 0; i < cnt; i++) {
        fac[i][0] = 1;
        p = pri[i];
        for (j = 1; j < p; j++)
            fac[i][j] = j * fac[i][j-1] % p;
    }
}

int *find(int p) {
    int l = 0, r = cnt - 1;
    while (l <= r) {
        int m = (l + r) / 2;
        if (pri[m] == p) return fac[m];
        else if (pri[m] < p) l = m + 1;
        else r = m - 1;
    }
    return fac[0];
}

int main() {
    int n, k, p, t = 1;
    init();
    while (scanf("%d %d %d", &n, &k, &p) != EOF) {
        if (k > n - k) k = n - k;
        printf("Case #%d: ", t++);
        printf("%d\n", (lucas(n+1, k, p, find(p)) + n - k) % p);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值