题目描述:
学校里有N个同学,老师要按照同学们的考试成绩给大家进行排名,一共有多少种不同的排名可能(成绩相同的同学不计名字次序,算一种排名)。
输入描述:
每组数据第一行一个整数N,代表有N同学(1 <= N < 19)。
输出描述:
对于每组数据输出一个整数占一行,代表N个同学之间的排名有多少种可能。
例子:
输入
2
3
输出
3
13
举例:
当N为2时,一共3种排名;当N为3时,一共13种排名。
递归法
假设共有n名同学排出m个名次,则共有f(n,m)
个排名方式。
- 若m=1,即n名同学共有1个名次,根据题意,此时共有1个排名方式,f(n,m)=1。
- 若m <1或m>n,不符合实际情况,此时无法计算排名方式的个数,f(n,m)=0。
- 若m>=1且m<=n,求n名同学排出m个名次时排名方式的个数。要令n名同学排出m个名次,那么n-1名同学就应该排出m-1个名次或m个名次。可利用n-1名同学的排名方式来计算总的排名个数(缩小一个问题规模),此时的排名方式分为两种情况:
n-1名同学共排出m-1个名次
这种情况下,要令n名同学排出m个名次,第n名同学的排名应与前m-1个名次均不同,但这个名次的位置共有m种取法。
比如:n-1名同学排出3个名次,分别是1、2、3。第n名同学的名次可选为_1_2_3_
中横线的部分,即在4个位置当中选择一个,共4种选择方式。
因此,这种情况下共有f(n-1,m-1)*m
种排名方式。
n-1名同学共排出m个名次
这种情况下,要令n名同学排出m个名次,第n名同学的排名应属于前m个名次中的一个。这个名次共有m种取法。
因此,这种情况下共有f(n-1,m)*m
种排名方式。
#include<stdio.h>
/**
* 计算f(n,m)
* n表示学生人数,m表示名次个数
*/
int compute(int n, int m)
{
if (m == 1)
return 1;
if ((m < 1) || (m > n))
return 0;
return compute(n - 1, m - 1) * m + compute(n - 1, m) * m;
}
int main()
{
int n;
int sum = 0;
scanf("%d", &n);
if ((n < 1) || (n > 19))
return 0;
for (int i = 1; i <= n; i++) {
sum = sum + compute(n, i);
}
printf("%d", sum);
return 0;
}
运行结果:
动态规划
递归法解决问题需要消耗大量的栈空间,同时会重复计算很多次。考虑使用动态规划的方法解决此问题。
用动态规划的方法计算学生排名方式的个数f(n,m)
,可以求出m、n与f(n,m)之间的递推公式。
已知:
当m=n时,f(n,m)=n! 无学生成绩并列
当m=1时,f(n,m)=1 所有学生均并列第一
当1<m<n时,f(n,m) = f(n-1,m-1)*m +f(n-1,m)*m
提前构造f(n,m)函数,利用二维数组计算出需要用到的所有值。
#include<stdio.h>
#include<string.h>
#define N 19
int main()
{
int n;
int sum = 0;
int f[N][N];
memset(f, 0, sizeof(f));
scanf("%d", &n);
if ((n < 1) || (n > N))
return 0;
//初始化f数组
for (int i = 1; i <= n; i++) {
f[i][1] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = 2; j <= i; j++) {
f[i][j] = f[i - 1][j - 1] * j + f[i - 1][j] * j;
}
}
for (int j = 1; j <= n; j++) {
sum = sum + f[n][j];
}
printf("%d", sum);
return 0;
}
参考:
https://blog.csdn.net/qq_27952053/article/details/96633796