题目链接:
题意:
给定 k,S(1<=k,S<=1e18),p(2<=p<=5e6),求 。
思路:
需要把这个公式转换成组合数学问题:
相当于有 S 个小球,分成 k 堆,可以有剩余小球,然后把每堆小球个数乘起来得到这种情况的价值,最后求的就是所有情况的价值之和。
每堆小球个数乘起来可以理解为:从每堆小球中任取一个,有多少种取法。
然后就考虑如何分组,使用隔板法。要分成 k 组,且可以有剩余,那么相当于加 k 个隔板。默认最前面有一个隔板(实际不存在),两个隔板之间为一个分组。(如图,S=8,k=3的情况)
我们把隔板也看作一个个小球,那么总共就有 k+S 个小球。从中任取 k 个作为隔板。这样会出现不合法的情况,即第一个为隔板或两个隔板是相邻的,这样就会有一些分组中小球个数为 0 个。
非常神奇的做法来了:
我们在这 k+S 个小球中任选 2k 个,其中第奇数个作为从一组中选出的小球,第偶数个作为隔板。这情况数就是答案!且巧妙的避开了上述的不合法情况!
最后, % p,因为 s,k 很大,但 p 较小,因此可以用 Lucas 定理进行计算:
C(n,m)%p = C(n/p,m/p)*C(n%p,m%p)%p 。
预处理C(p,p)以内的组合数,然后递归计算即可。
Code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 5e6+ 10;
ll k, s, p;
ll F[MAX], Finv[MAX], inv[MAX];//F是阶乘,Finv是逆元的阶乘
void init(ll MOD) {
inv[1] = 1;
for (int i = 2; i < MAX; i++) {
inv[i] = (MOD - MOD / i) * 1ll * inv[MOD % i] % MOD;
}
F[0] = Finv[0] = 1;
for (int i = 1; i < MAX; i++) {
F[i] = F[i - 1] * 1ll * i % MOD;
Finv[i] = Finv[i - 1] * 1ll * inv[i] % MOD;
}
}
ll comb(ll n, ll m, ll MOD) {//comb(n, m)就是C(n, m)
if (m < 0 || m > n) return 0;
return F[n] * 1ll * Finv[n - m] % MOD * Finv[m] % MOD;
}
ll cul(ll n, ll m, ll MOD)
{
if (m <= MOD && n <= MOD) {
return comb(n, m, MOD);
}
return cul(n / MOD, m / MOD, MOD)*comb(n%MOD, m%MOD, MOD) % MOD;
}
int main()
{
scanf("%lld%lld%lld", &k, &s, &p);
if (s < k) {
printf("0\n");
return 0;
}
init(p);
ll ans = cul(s + k, 2 * k, p);
printf("%lld\n", ans);
return 0;
}