1. 程式人生 > >HDU 3037 Saving Beans (隔板法 Lucas定理 費馬小定理 乘法逆元)

HDU 3037 Saving Beans (隔板法 Lucas定理 費馬小定理 乘法逆元)

Saving Beans

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7780    Accepted Submission(s): 3135

 

Problem Description

Although winter is far away, squirrels have to work day and night to save beans. They need plenty of food to get through those long cold days. After some time the squirrel family thinks that they have to solve a problem. They suppose that they will save beans in n different trees. However, since the food is not sufficient nowadays, they will get no more than m beans. They want to know that how many ways there are to save no more than m beans (they are the same) in n trees.

Now they turn to you for help, you should give them the answer. The result may be extremely huge; you should output the result modulo p, because squirrels can’t recognize large numbers.

Input

The first line contains one integer T, means the number of cases.

Then followed T lines, each line contains three integers n, m, p, means that squirrels will save no more than m same beans in n different trees, 1 <= n, m <= 1000000000, 1 < p < 100000 and p is guaranteed to be a prime.

Output

You should output the answer modulo p.

Sample Input

2

1 2 5

2 1 5

Sample Output

3

題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=3037

題目大意:有n個盒子,最多有m個球,求可能的放球方案數對p取模,p保證是質數

題目分析:首先幾個概念

1)隔板法:

        a)盒子不允許為空:m個球放成一條線,則存在m-1個空隙,用n-1個隔板將其分成n份,方案數為C[m-1][n-1]

        b)盒子允許為空:將a)中每個盒子各取出一個球,則問題轉換成1),方案數為C[n+m-1][n-1]

2)Lucas定理:

        在n和m很大,p相對比較小且為質數時,可以使用Lucas定理求組合數

        公式:lucas(n, m) = (C(n % p, m % p) * lucas(n / p, m / p)) % p

3)費馬小定理:

        若gcd(a, p) = 1,則a^(p-1)≡1 mod p

4)乘法逆元:

        若a*b≡1 mod p,則a和b互為乘法逆元,記為inv[a]=b,inv[b]=a,根據費馬小定理:a*a^(p-2)≡1 mod p,因此一個數x關於1模p的乘法逆元即為x^(p-2)

5)除法取模

        (a/b)%p = (a*inv[b])%p=(a*b^(p-2))%p

回到題目,若恰有m個球,根據隔板法b)情況,可得公式C[n+m-1][n-1],最多m個球時則答案為(0<=i<=m)∑C[n+i-1][n-1] =>(0<=i<=m)∑C[n+i-1][i]=C[n-1][0]+C[n][1]+C[n+1][2]+...+C[n+m-1][m]

根據組合數公式C[n][m]=C[n-1][m]+C[n-1][m-1],因為C[n-1][0]=C[n][0],則可以得到C[n][0]+C[n][1]=C[n+1][1],

C[n+1][1]+C[n+1][2]=C[n+2][2],...,C[n+m-1][m-1]+C[n+m-1][m]=C[n+m][m],故最後答案為C[n+m][m]

#include <cstdio>
#define ll long long
using namespace std;
int n, m, p;

ll qpow(ll x, int n) {
    ll ans = 1;
    while (n) {
        if (n & 1) {
            ans = (ans * x) % p;
        }
        x = (x * x) % p;
        n >>= 1;
    }
    return ans;
}

ll C(int n, int m) {
    if (n < m) {
        return 0;
    }
    if (m == 0) {
        return 1;
    }
    if (n - m < m) {
        m = n - m;
    }
    ll a = 1, b = 1;
    while (m) {
        a = (a * n) % p;
        b = (b * m) % p;
        n--;
        m--;
    }
    return (a * qpow(b, p - 2)) % p;
}

ll lucas(int n, int m) {
    if (m == 0) {
        return 1;
    }
    return (C(n % p, m % p) * lucas(n / p, m / p)) % p;
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        scanf("%d %d %d", &n, &m, &p);
        printf("%d\n", lucas(n + m, m));
    }
}