算法笔记5.8 组合数
原创
©著作权归作者所有:来自51CTO博客作者武大保安的原创作品,请联系作者获取转载授权,否则将追究法律责任
1.关于n!的一个问题
问题描述:求n!中有多少个质因子p.(注意是质因子,不是因子)
此题是5.5节质因子分解中的一个小分支,当时求质因子这块用的就是最笨的暴力枚举法,研究完本节,则前面的算法还可进一步优化!
方法1.暴力枚举法
//暴力计算n!中有多少个质因子p
int cal(int n,int p){
int ans=0;
for(int i=2;i<=n;i++){
int temp=i;
while(temp%p==0){
ans++;
temp/=p;
}
}
return ans;
}
样例:
#include<iostream>
using namespace std;
//暴力计算n!中有多少个质因子p
int cal(int n,int p){
int ans=0;
for(int i=2;i<=n;i++){
int temp=i;
while(temp%p==0){
ans++;
temp/=p;
}
}
return ans;
}
int main(){
int n,p;
while(cin>>n>>p){
cout<<n<<"!中质因子"<<p<<"的个数为:"<<cal(n,p)<<endl;
}
return 0;
}
过了好几秒
方法2.高效算法
n!中有{n/p+n/p^2+n/p3+...}个质因子p
//利用公式求n!中质因子p的个数
int cal(int n,int p){
int ans=0;
while(n){
ans+=n/p;
n/=p;110
}
return ans;
}
样例:
#include<iostream>
using namespace std;
//利用公式求n!中质因子p的个数
int cal(int n,int p){
int ans=0;
while(n){
ans+=n/p;
n/=p;
}
return ans;
}
int main(){
int n,p;
while(cin>>n>>p){
cout<<n<<"!中质因子"<<p<<"的个数为:"<<cal(n,p)<<endl;
}
return 0;
}
补充:递归写法
//利用公式求n!中质因子p的个数
int cal(int n,int p){
if(n<p) return 0;
return n/p+cal(n/p,p);
}
样例:
#include<iostream>
using namespace std;
//利用公式求n!中质因子p的个数
int cal(int n,int p){
if(n<p) return 0;
return n/p+cal(n/p,p);
}
int main(){
int n,p;
while(cin>>n>>p){
cout<<n<<"!中质因子"<<p<<"的个数为:"<<cal(n,p)<<endl;
}
return 0;
}
O(logn)即使递归也很快
2.组合数的计算
此处主要求C(n,m) n>=m 从n个物品中取出m个物品的取法
方法一:通过定义式直接计算
即使用long long 也只能计算n<=20的数据 结果本身可能很小,但n!却因为太大而溢出
typedef long long ll;
ll C(ll n,ll m){
ll ans=1;
//n!
for(ll i=1;i<=n;i++){
ans*=i;
}
//除以m!
for(ll i=1;i<=m;i++){
ans/=i;
}
//除以(n-m)!
for(ll i=1;i<=n-m;i++){
ans/=i;
}
return ans;
}
样例:
#include<iostream>
using namespace std;
typedef long long ll;
ll C(ll n,ll m){
ll ans=1;
//n!
for(ll i=1;i<=n;i++){
ans*=i;
}
//除以m!
for(ll i=1;i<=m;i++){
ans/=i;
}
//除以(n-m)!
for(ll i=1;i<=n-m;i++){
ans/=i;
}
return ans;
}
int main(){
int n,m;
while(cin>>n>>m){
cout<<"C("<<n<<","<<m<<")="<<C(n,m)<<endl;
}
return 0;
}
n>21就无法计算了
方法2:利用递推公式计算
c(n,m)=c(n-1,m-1)+c(n-1,m)
c(0,0)=c(n,n)=1正好作为递归边界
typedef long long ll;
const int N=67;
ll res[N][N]={0};
ll C(ll n,ll m){
if(m==0 || m==n) return 1;
if(res[n][m]!=0) return res[n][m];//减少递归次数 提高效率
return res[n][m]=C(n-1,m)+C(n-1,m-1);
}
样例:
#include<iostream>
using namespace std;
typedef long long ll;
const int N=67;
ll res[N][N]={0};
ll C(ll n,ll m){
if(m==0 || m==n) return 1;
if(res[n][m]!=0) return res[n][m];//减少递归次数 提高效率
return res[n][m]=C(n-1,m)+C(n-1,m-1);
}
int main(){
ll n,m;
while(cin>>n>>m){
cout<<"C("<<n<<","<<m<<")="<<C(n,m)<<endl;
}
return 0;
}
将递推改成直接计算
//直接计算出整张表 其实就是求杨辉三角
const int n=20+1;
long long res[n][n]={0};
void calC(){
for(int i=0;i<n;i++){
res[i][0]=res[i][i]=1;//初始化边界
}
//利用杨辉三角理解
for(int i=2;i<n;i++){//00 11 10 都算出来
for(int j=0;j<=i/2;j++){
res[i][j]=res[i-1][j]+res[i-1][j-1];
res[i][i-j]=res[i][j];//另外半部分直接算出来
}
}
}
样例
#include<iostream>
using namespace std;
//直接计算出整张表 其实就是求杨辉三角
const int n=20+1;
long long res[n][n]={0};
void calC(){
for(int i=0;i<n;i++){
res[i][0]=res[i][i]=1;//初始化边界
}
//利用杨辉三角理解
for(int i=2;i<n;i++){//00 11 10 都算出来
for(int j=0;j<=i/2;j++){
res[i][j]=res[i-1][j]+res[i-1][j-1];
res[i][i-j]=res[i][j];//另外半部分直接算出来
}
}
}
int main(){
calC();//先直接算出整张表来
// int n,m;
// while(cin>>n>>m){
// cout<<"C("<<n<<","<<m<<")="<<res[n][m]<<endl;
// }
int N;
cin>>N;
for(int i=0;i<N;i++){
for(int j=0;j<=i;j++){
cout<<res[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
方法二N取大了没用,(67,33)开始溢出
下面方法(62,31开始溢出)
方法三:通过定义式的变形来计算
eg:C(9,4)
6 7 8 9
-×-×-×-
1 2 3 4
先计算6/1再乘上7/2再乘上8/3再乘上9/4
而6/1=c(6,1)
(6*7)/(1*2)=c(7,2)
(6*7*8)/(1*2*3)=c(8,3)
... 一定都是整数,即都能整除
typedef long long ll;
ll calC(ll n,ll m){
ll ans=1;
for(ll i=1;i<=m;i++){
ans=ans*(n-m+i)/i;
}
return ans;
}
样例:
#include<iostream>
using namespace std;
typedef long long ll;
ll calC(ll n,ll m){
ll ans=1;
for(ll i=1;i<=m;i++){
ans=ans*(n-m+i)/i;
}
return ans;
}
int main(){
int n,m;
while(cin>>n>>m){
cout<<"C("<<n<<","<<m<<")="<<calC(n,m)<<endl;
}
return 0;
}
3.计算C(n,m)%p
暂且写下最简单最实用的方法一
方法一:对(res[i-1][j]+res[i-1][j-1])相加后整体取余就不会错的
const int N=1010;
int res[N][N]={0};
//递归
int calC(int n,int m,int p){
if(m==0||m==n) return 1;
if(res[n][m]!=0) return res[n][m];
return res[n][m]=(calC(n-1,m,p)+calC(n-1,m-1,p))%p;
}
样例:
#include<iostream>
using namespace std;
const int N=1010;
int res[N][N]={0};
//递归
int calC(int n,int m,int p){
if(m==0||m==n) return 1;
if(res[n][m]!=0) return res[n][m];
return res[n][m]=(calC(n-1,m,p)+calC(n-1,m-1,p))%p;
}
int main(){
int m,n,p;
while(cin>>n>>m>>p){
cout<<"C("<<n<<","<<m<<")%"<<p<<"="<<calC(n,m,p)<<endl;
}
return 0;
}
方法一非递归写法:
#include<iostream>
using namespace std;
const int N=1010;
int res[N][N]={0};
//非递归
int n=1000;
int p=10000;
void calC(){
for(int i=0;i<=n;i++){
res[i][0]=res[i][i]=1;
}
for(int i=2;i<=n;i++){
for(int j=1;j<=i/2;j++){
res[i][j]=(res[i-1][j]+res[i-1][j-1])%p;
res[i][i-j]=res[i][j];
}
}
}
int main(){
calC();
int m,n;
while(cin>>n>>m){
cout<<"C("<<n<<","<<m<<")%"<<p<<"="<<res[n][m]<<endl;
}
return 0;
}