《编程思维与实践》1067.小型组合数
题目
思路
法一:
注意到题目数据最大为 C 40 20 = 137846528820 C_{40}^{20}=137846528820 C4020=137846528820在long long的范围内,所以其实可以不用大整数的处理方法去计算:
由于 C m n = m ! n ! ( m − n ) ! = m ( m − 1 ) . . . ( m − ( n − 1 ) ) n ( n − 1 ) . . . 2 ⋅ 1 C_m^n=\frac{m!}{n!(m-n)!}=\frac{m(m-1)...(m-(n-1))}{n(n-1)...2\cdot 1} Cmn=n!(m−n)!m!=n(n−1)...2⋅1m(m−1)...(m−(n−1)) , 注意到 1 ∣ m , 2 ∣ m ( m − 1 ) , 3 ∣ m ( m − 1 ) ( m − 2 ) … 1|m,2|m(m-1),3|m(m-1)(m-2)… 1∣m,2∣m(m−1),3∣m(m−1)(m−2)…
这是因为连续出现的n个数字的乘积必然会存在因子n(可用归纳法证明).
所以只需要将结果依次乘m(m-1)…(m-(n-1))的每一位,然后依次除去因子1,2,3….n即可.
注意的点:
中间步骤可能数据会超过long long的范围, 应该用unsigned long long去存.
法二:
用大整数的处理方式去计算,但需要运用组合数的递推公式 C m n = C m − 1 n + C m − 1 n − 1 C_m^n=C_{m-1}^{n}+C_{m-1}^{n-1} Cmn=Cm−1n+Cm−1n−1 ;
记dp[n][m]=dp[n][m-1]+dp[n-1][m-1],
初始化dp[0][m]=1,dp[m][0]=0,
之后先遍历n(从1到40)再遍历m(从1到40).
代码
法一:
#include<stdio.h>
int main()
{
int T;
scanf("%d",&T);
for(int t=0;t<T;t++)
{
int m,n;
scanf("%d %d",&m,&n);
unsigned long long ans=1;
for(int j=0;j<n;j++)
{
ans*=m-j;
ans/=j+1;
}
printf("case #%d:\n",t);
printf("%llu\n",ans);
}
return 0;
}
法二:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define N 100
#define L 40 // dp[40][40]
typedef struct{int cnt,v[N];}BIGINT;
BIGINT carry(BIGINT S,int n); //进位 n表示进制
BIGINT int2BIG(int x,int bin); //int 转换(to)成BIGINT
BIGINT add(BIGINT S, BIGINT T); //两个大整数相加
int main()
{
int T;
scanf("%d",&T);
BIGINT dp[L+1][L+1];
for(int i=0;i<L+1;i++)
{
for(int j=0;j<L+1;j++)
{
dp[i][j]=int2BIG(0,10);
}
}
for(int j=0;j<L+1;j++)
{
dp[0][j]=int2BIG(1,10);
}
for(int i=1;i<L+1;i++)
{
for(int j=1;j<L+1;j++)
{
dp[i][j]=add(dp[i][j-1],dp[i-1][j-1]);
}
}
for(int t=0;t<T;t++)
{
int m,n;
scanf("%d%d",&m,&n);
printf("case #%d:\n",t);
for(int i=dp[n][m].cnt-1;i>=0;i--) //逆向输出
{
printf("%d",dp[n][m].v[i]);
}
printf("\n");
}
return 0;
}
BIGINT carry(BIGINT S,int n) //进位 n表示进制
{
int flag=0;
for(int i=0;i<S.cnt;i++)
{
int temp=S.v[i]+flag;
S.v[i]=temp%n;
flag=temp/n;
}
if(flag) //为了加法进行的方便 可能多一位
{
S.v[S.cnt++]=flag;
}
return S;
}
BIGINT int2BIG(int x,int bin) //int 转换(to)成BIGINT
{
BIGINT R={0,{0}};
do
{
R.v[R.cnt++]=x%bin;
x/=bin;
}while(x>0);
return R;
}
BIGINT add(BIGINT S, BIGINT T) //两个大整数相加
{
int max=S.cnt>T.cnt?S.cnt:T.cnt;
BIGINT R={max,{0}};
for(int i=0;i<max;i++)
{
R.v[i]=S.v[i]+T.v[i]; //依次进行普通乘法
}
R=carry(R,10);
return R;
}