HDU 3037 Saving Beans 组合数递归公式 套卢卡斯定理模板

传送门

这题是求 0~m个果子放在不同的 n 棵树上有多少种放置方法

首先看 m 个果子的情况,用隔板法来处理这个情况。最多有 n-1 棵树能空着,那就加 n-1 个气泡进去。然后选择 n-1 个位置就把 m+n-1 分成了 n 段(n 棵树的情况)。这个对应的选择方式就是有 C(n+m-1, n-1),其实也就是 C(n+m-1, m)

那么果子有 0~m 种,所有总和就是
ΣC(n+i-1,i) (i 从 0 到 m)
我看展开这个公式看一眼
C(n-1,0)+C(n,1)+C(n+1,2)+C(n+2,3)+…+C(n+m-1,m)

我们回忆曾经学过的组合数递归公式
C(n,m)=C(n-1,m)+C(n-1,m-1)

由于 C(n-1,0) = C(n,0) = 1 ,那么我们把上面式子的第一项换成 C(n,0)
整个式子就变成了
C(n,0)+C(n,1)+C(n+1,2)+C(n+2,3)+…+C(n+m-1,m)
前两项可以结合成 C(n+1,1) ,整个式子就成了
C(n+1,1)+C(n+1,2)+C(n+2,3)+…+C(n+m-1,m)
又可以继续合并,合并到最后结果就是
C(n+m,m)

这时候我们发现 n 和 m 非常的大,相对来说 p 比较小
那我们就可以用 卢卡斯定理模板

#include 
using namespace std;
using ll = long long;
ll n,m,p;
ll quick_mod(ll a, ll b, ll m) {
    ll ans = 1;
    while (b) {
        if (b & 1) {
            ans = ans * a % m;
        }
        a = a * a % m;
        b >>= 1;
    }
    return ans;
}
 
ll comp(ll a, ll b, ll m) {
    if (a < b) {
        return 0;
    }
    if (a == b) {
        return 1;
    }
    if (b > a - b) {
        b = a - b;
    }
    ll ans = 1, ca = 1, cb = 1;
    for (int i = 0; i < b; i++) {
        ca = ca * (a - i) % m;
        cb = cb * (b - i) % m;
    }
    ans = ca * quick_mod(cb, m - 2, m) % m;
    return ans;
}
 
ll lucas(ll a, ll b, ll m) {
    ll ans = 1;
    while (a && b) {
        ans = (ans * comp(a % m, b % m, m)) % m;
        a /= m;
        b /= m;
    }
    return ans;
}
 
int main() {
	std::ios::sync_with_stdio(false);
	int T;
	cin >> T;
	long long n, p, m;
	while (T--) {
		cin >> n >> m >> p;
		cout << lucas(n+m, n, p) << endl;	
	}

	return 0;
}

你可能感兴趣的:(杂谈)