两种组合数问题
今天学了一点组合数学, 一个求组合数的算法, 一个是求组合数对p取模
一: 求组合数C(n, m)
方法一
此方法是最粗暴的方法, 按照组合数的定义直接模拟, 不过这种方法的弊端是, 即使组合数不算大, 在它的中间过程中可是有可能溢出的. 即使是long long, 也有很大风险.
思路较为简单, 代码不展示
方法二
此方法利用组合数的递推公式
排列组合里面有很多递推公式, 巧妙地应用会有意想不到的妙处.
这里我们用
C(n, m) = C(n - 1, m) + C(n - 1, m - 1)
可以用递归实现这个过程
最需要注意的是递归的边界, 大家知道组合数组成一个图形地话其实是一个杨辉三角, 所以我们这里递归地边界也就是三角的两条边, 即m == n || m = 0时
递归的过程中用到记忆化搜索, 把之前求过的组合数存储起来.
也可以用递推实现, 和递归本质上时一样的.
代码
#include <iostream>
using namespace std;
long long exi[100][100] = {0};
long long zh(int n, int m)
{
if (m == 0 || m == n) return 1;
if (exi[n][m]) return exi[n][m];
else return exi[n][m] = zh(n - 1, m) + zh(n - 1, m - 1);
}
int main()
{
int n, m;
cin >> n >> m;
cout << zh(n, m) << endl;
}
尤其要注意递归的边界
时间复杂度不超过O(n^2)
方法三
这种方法甚是巧妙, 较好地规避了数据溢出的情况, 虽然和方法二相比还是有些不足, 但作为一种启发, 还是非常有营养的.
书上把它叫做通过定义式的变形来计算
方法的表述有点复杂, 符号什么的不好写, 只放代码咯.
#include <iostream>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
long long ans = 1;
for (int i = 1; i <= m; ++i) {
ans = ans * (n - m + i) / i;
}
cout << ans;
}
似乎这几种方法中这种是最简洁的. 时间复杂度为O(m)
以上三种方法, 方法一最粗暴, 方法二可以存储中间值, 方法三代码简单, 防溢出效果稍差. 各有利弊吧.