可并列排名问题(疯狂游戏笔试编程题)

题目描述:

n个人排名,允许并列名次,共有多少种排名结果?(1 <= n < 19)

示例:

n = 1时,结果为1,只有一种排名结果。

n = 2时,结果为3,即 a = b, a > b, b > a 三种情况。

n = 3时,结果为13,下面列举:
a = b = c
a > b > c
a > b = c
a > c > b
a = b > c
a = c > b
b > a > c
b > a = c
b > c > a
c > a > b
c > a = b
c > b > a
c = b > a

题目分析:

本题可以采用递归的解法,基于递归的思想可改为动态规划的方法。
下面分析一下本题采用的递推思路。
我们从上面的 n=3 时的举例分析,可以看出,3个人就有1~3共3种名次,那么只有1种名次便是3个人名次都相同的情况,2种名次就是有两个人名次相同的情况,3种名次就是3个人名次都不相同的情况(即 n ! n! n! 种情况)。

那么接下来,假设有 n n n 个人,排出 m m m 个名次( 注意 m m m在这里是固定的 ),有 f ( n , m ) f(n, m) f(n,m) 种结果 ( 1 < = m < = n ) (1 <= m <= n) 1<=m<=n
m m m 1 1 1 时, f ( n , m ) = 1 f(n ,m) = 1 f(n,m)=1;
1 < m < = n 1 < m <= n 1<m<=n 时,对于新加入的1个人与前面 n − 1 n - 1 n1 个人的名次关系存在两种情况:
新加入的人与前面 n − 1 n - 1 n1 个人存在并列排名关系,那么前面 n − 1 n - 1 n1 个人便可排出 m m m 个名次,而新加入的人有可能与这 m m m 个名次中的任意一个重复,因此这种情况便有 f ( n − 1 , m ) ∗ m f(n - 1, m) * m f(n1,m)m 种结果。
新加入的人与前面 m − 1 m-1 m1 个人都不并列,那么前面 n − 1 n-1 n1 个人便可排出 m − 1 m-1 m1 个名次,那么新加入的人可能是 m m m 个名次中任意一个,这样便可和前面 n − 1 n - 1 n1 个人构成 m m m 个名次了,因此这种情况便有 f ( n − 1 , m − 1 ) ∗ m f(n - 1, m - 1)* m f(n1,m1)m 种结果.。

那么综合上述两种情况, f ( n , m ) = m ∗ ( f ( n − 1 , m ) + f ( n − 1 , m − 1 ) ) f(n, m) = m * (f(n - 1, m) + f(n - 1, m - 1)) f(n,m)=m(f(n1,m)+f(n1,m1))

我们接下来做以实现:

/* 返回值是long long 类型,因为 n大于12时会溢出 */
long long orderFun(int n, int m)
{
	if (m == 1)
		return 1;
	/* 由于我们递归时n和m都在减少,有可能出现n比m小的情况 */
	if (m > n)
		return 0;
	/* 递归计算 */
	return m * (orderFun(n - 1, m) + orderFun(n - 1, m - 1));
}

long long orderFun(int n)
{
	if (n < 1)
		return 0;
	
	long long res = 0;
	/* 将n个人产生的所有排名结果的总和相加得到最终结果 */
	for (int k = 1; k <= n; k++)
	{
		res += orderFun(n, k);
	}

	return res;
}

int main()
{
	for (int i = 1; i < 19; i++)
	{
		cout << i << " : " << orderFun(i) << endl;
	}
}

上面我们使用了递归的解法,因此我们是可以修改为动态规划解法的,使用动态规划,我们不会产生大量的函数调用开销,一次调用,便可计算出结果。

我们可以根据上面的递归函数得出dp数组的含义,首先是一个二维数组。 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 i i i 个人, j j j 种排名所对应的排名结果。

那么状态转移方程就为
d p [ i ] [ j ] = { 1 j = 1 j ∗ ( d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − 1 ] ) 1 < j < i dp[i][j]=\left\{ \begin{array}{lcl} 1&& { j=1}\\ j *(dp[i-1][j]+dp[i-1][j-1]) && {1<j<i}\\ \end{array}\right. dp[i][j]={1j(dp[i1][j]+dp[i1][j1])j=11<j<i

代码如下:

long long orderFun(int n)
{
	vector<vector<long long>> dp(n + 1, vector<long long>(n + 1, 0));
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			if (j == 1)
				dp[i][j] = 1;
			else
			{
				dp[i][j] = j * (dp[i - 1][j - 1] + dp[i - 1][j]);
			}
		}
	}
	/* 累加n个人j种排名的所有结果,得到最终结果 */
	long long res = 0;
	for (int j = 1; j <= n; j++)
	{
		res += dp[n][j];
	}
	return res;
}

int main()
{
	for (int i = 1; i < 19; i++)
	{
		cout << i << " : " << orderFun(i) << endl;
	}
}

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZY-JIMMY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值