本文共 2229 字,大约阅读时间需要 7 分钟。
隔板法可以用来求将n分解为k个非负整数之和的解的数量,这在组合数学中被广泛应用。对于给定的n和k,我们需要计算组合数C(n + k -1, k -1),这表示将n个物体分成k个部分的方式数。
为了高效计算这个组合数,我们可以利用模运算的性质和快速幂算法来处理大数。以下是详细的实现思路:
预计算阶乘和逆阶乘:
快速幂算法:
计算组合数的公式:
以下是代数运算的优化:
以下是实现代码:
#include#include #include #include #include #include using namespace std;const long long MOD = 1000000007;typedef long long LL;LL quick_pow(LL a, LL b, LL mod) { LL result = 1; a %= mod; while (b > 0) { if (b % 2 == 1) { result = (result * a) % mod; } a = (a * a) % mod; b /= 2; } return result;}void init_dp(LL dp[], int size) { dp[0] = 1; for (int i = 1; i < size; ++i) { dp[i] = (dp[i-1] * i) % MOD; }}int main() { // 预计算阶乘和逆阶乘到 2*1e6 +5 const int MAX = 2 * 10^6 + 5; LL dp[MAX + 1]; init_dp(dp, MAX + 1); // 读取输入并处理每个测试用例 int T; scanf("%d", &T); for (int i = 1; i <= T; ++i) { int n, k; scanf("%d %d", &n, &k); // 计算C(n +k -1, k-1) mod MOD // 由于组合数的计算公式为 C(a, b) = f(a) / (f(b) * f(a-b)) mod MOD // 我们可以用预计算的dp数组 // 其中,f(a) = dp[a] // f(b) = dp[b] // f(a-b) = dp[a-b] // 这里b = k-1,a = n +k -1 LL a = n + k - 1; LL b = k - 1; LL c = a - b; LL numerator = dp[a]; LL denominator = (dp[b] * dp[c]) % MOD; LL inv_denominator = quick_pow(denominator, MOD-2, MOD); LL result = (numerator * inv_denominator) % MOD; printf("Case %d: %lld\n", i, result); } return 0;}
代码注释:
预计算阶乘:init_dp
函数预计算了从0到2*1e6+5的阶乘,并对每个值取模10^9+7。这样可以避免在每次查询时重新计算阶乘,提高效率。
快速幂函数:quick_pow
函数用于计算模逆,通过快速幂算法高效地计算模逆,而不是暴力试验每一个可能的逆元。
处理每个测试用例:对于每个测试用例,计算所需的组合数。通过公式将组合数分解为阶乘和逆阶乘的乘积,利用预计算的阶乘和快速幂得到的逆阶乘来计算结果。
优化点:
注意事项:
确保预计算的阶乘数组大小足够大,能够处理最大的n和k。⟨提示:当n和k都达到1e6时,最大的组合数n +k -1会是2*1e6,因此预计算到2e6 +5是正确的。⟩
乘法和模运算要仔细处理,避免溢出或计算错误。
扩展性:
测试样例:
按照问题中的示例,输入:
4
4 33 51000 31000 5输出结果应为:
15
3550150184793457可以验证代码是否得到这些预期结果。
转载地址:http://jzwfk.baihongyu.com/