算法笔记5.8 组合数

版权声明:原创文章,转载请注明出处 https://blog.csdn.net/hza419763578/article/details/88203168

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;
}

猜你喜欢

转载自blog.csdn.net/hza419763578/article/details/88203168
今日推荐