知识背景
排列数公式: A n m = n × ( n − 1 ) × . . . × ( n − m + 1 ) = n ! ( n − m ) ! A_n^m = n×(n-1)×...×(n-m+1) = \frac{n!}{(n-m)!} Anm=n×(n−1)×...×(n−m+1)=(n−m)!n!
组合数公式: C n m = n × ( n − 1 ) × . . . × ( n − m + 1 ) m × ( m − 1 ) × . . . × 1 = n ! m ! ( n − m ) ! C_n^m = \frac{n×(n-1)×...×(n-m+1)}{m×(m-1)×...×1} = \frac{n!}{m!\space(n-m)!} Cnm=m×(m−1)×...×1n×(n−1)×...×(n−m+1)=m! (n−m)!n!
代码实现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll A (int n, int m) {
ll res = 1;
for (int i = 0;i < m;i ++)
res *= n - i;
return res;
}
ll C (int n, int m) {
ll res = 1;
for (int i = 0;i < m;i ++) {
res *= n - i;
res /= i + 1;
}
return res;
}
int main()
{
int n, m;
cin >> n >> m;
cout << A (n, m) << endl << C (n, m) << endl;
return 0;
}
如果题目要求取模,则在乘法进行时进行取模操作即可。
思考 C n m C_n^m Cnm的实现
显然,如果我们要是将 C n m C_n^m Cnm的核心代码改写成:
for (int i = 0;i < m;i ++) {
res *= n - i;
res /= m - i;
}
改写后,我们计算的方式为:n/m × (n-1)/(m-1) × ... × (n-m+1)/1
,由于这其中的除法为整除,但是我们并不能保证(n-i)/(m-i)
一定能够整除,所以这大概率会影响结果,导致结果错误。
而正确的计算方式就是“代码实现”中给出的:n/1 × (n-1)/2 × (n-3)/3 × ... × (n-m+1)/m
。
这样计算之所以正确是因为连续的i
个数中必然存在能整除i
的数,比如,三个数1、2、3中存在3可以被3整除,三个数5、6、7中存在6可以被3整除……
也就是说,第一项n
必然可以被1整除,前两项n × (n-1)
也可以被2整除,前三项n × (n-1) × (n-2)
可以被3整除……
按照这种计算方式完全可以保证没有精度的损失,每次都是整除。
这就是为什么我们计算组合数时,分子和分母一个正序乘一个逆序乘的原因。