| ||||||||||
Online Judge | Problem Set | Authors | Online Contests | User | ||||||
---|---|---|---|---|---|---|---|---|---|---|
Web Board Home Page F.A.Qs Statistical Charts | Current Contest Past Contests Scheduled Contests Award Contest |
组合数求解,及其优化。首先问题可以转化成“n + m 次决策,其中有 m 次向右”,也就变成了计算 C(n + m, m),当然根据组合数的对称性也可以计算 C(n + m, n). 0 <= n,m <= 2^{32} 所以直接计算组合数或者递推都不太可能。 然后 C(x, 0), C(x, 1), C(x, x) 是可能存在的 corner case 而且是能 O(1) 得到答案的特殊情况,考虑特判。 对于组合数 C(N, x) 来说,根据 C(N, N-x) = C(N, x) 的对称性和公式的分母变化可以得出它是一个单峰函数,当取到 C(N, N/2) 时最大。又因为题目答案不超过 32 位无符号整数,所以 C(N, N/2) 里的 N/2 最多也就是 34。 比较精华的是考虑怎么计算 C(n + m, min(n, m)) 这个式子。令 m < n ,可以推式子得到如下: C(n + m, m) = (n + m) 的 m 次下降幂 / m 的阶乘 所以可以只考虑 m 个多项式相乘的分母和一个阶乘分子即可。 既然 m 比较小,就考虑能不能将分子和 {1, 2, 3, ..., m} (也就是分母阶乘的每个部分)进行约分,使得计算过程不会溢出 32 位无符号整数。而且必然是可以约分的,因为分式的结果是个整数。而且每轮约分优先从大数字开始,因为它们可能含有更多的因子,不过 m 不大也无所谓了。 最后答案就是分子 m 个元素相乘。 ```core code (cpp) typedef unsigned u32; u32 gcd(u32 a, u32 b) { return b == 0 ? a : gcd(b, a % b); } u32 solve(u32 n, u32 m) { // 1. corner case if (n == m || m == 0) return 1; if (m == 1) return n; // ans = C(n + m, min(n, m)) = (n+m)! / n!m! std::vector<u32> denom(m); // 2. m is smaller for (u32 i = 0; i < m; i++) denom[i] = n - i; for (u32 i = 2; i <= m; i++) { for (u32 j = 0, d = i; j < m && d > 1; j++) { u32 g = gcd(d, denom[j]); d /= g; denom[j] /= g; } } u32 res = 1; for (int i = 0; i < m; i++) res *= denom[i]; return res; } int main() { int _ = 1; // scanf("%d", &_); for (u32 n, m; ; ) { scanf("%u%u", &n, &m); if (n == 0 && m == 0) break; printf("%u\n", solve(n + m, std::min(n, m))); } return 0; } ``` Followed by:
Post your reply here: |
All Rights Reserved 2003-2013 Ying Fuchen,Xu Pengcheng,Xie Di
Any problem, Please Contact Administrator