题目描述:
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
n−1 个人的名次关系存在两种情况:
① 新加入的人与前面
n
−
1
n - 1
n−1 个人存在并列排名关系,那么前面
n
−
1
n - 1
n−1 个人便可排出
m
m
m 个名次,而新加入的人有可能与这
m
m
m 个名次中的任意一个重复,因此这种情况便有
f
(
n
−
1
,
m
)
∗
m
f(n - 1, m) * m
f(n−1,m)∗m 种结果。
② 新加入的人与前面
m
−
1
m-1
m−1 个人都不并列,那么前面
n
−
1
n-1
n−1 个人便可排出
m
−
1
m-1
m−1 个名次,那么新加入的人可能是
m
m
m 个名次中任意一个,这样便可和前面
n
−
1
n - 1
n−1 个人构成
m
m
m 个名次了,因此这种情况便有
f
(
n
−
1
,
m
−
1
)
∗
m
f(n - 1, m - 1)* m
f(n−1,m−1)∗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(n−1,m)+f(n−1,m−1))
我们接下来做以实现:
/* 返回值是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[i−1][j]+dp[i−1][j−1])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;
}
}