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

算法笔记5.8 组合数_组合数

过了好几秒

算法笔记5.8 组合数_i++_02

 

 

 

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

算法笔记5.8 组合数_i++_03

 

补充:递归写法

//利用公式求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)即使递归也很快

算法笔记5.8 组合数_组合数_04

 

2.组合数的计算

算法笔记5.8 组合数_#include_05

算法笔记5.8 组合数_质因子_06

此处主要求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;
}

算法笔记5.8 组合数_质因子_07

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

算法笔记5.8 组合数_#include_08

 

将递推改成直接计算

//直接计算出整张表  其实就是求杨辉三角
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;
}

算法笔记5.8 组合数_质因子_09

方法二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;
}

算法笔记5.8 组合数_#include_10

 

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

算法笔记5.8 组合数_#include_11

 

方法一非递归写法:

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

算法笔记5.8 组合数_组合数_12