不容易系列之一(九度教程第 94 题)
1.题目描述:
大家常常感慨,要做好一件事情真的不容易,确实,失败比成功容易多了!做好“一件”事情尚且不易,若想永远成功而总从不失败,那更是难上加难了,就像花钱总是比挣钱容易的道理一样。话虽这样说,我还是要告诉大家,要想失败到一定程度也是不容易的。比如,我高中的时候,就有一个神奇的女生,在英语考试的时候,竟然把 40 个单项选择题全部做错了!大家都学过概率论,应该知道出现这种情况的概率,所以至今我都觉得这是一件神奇的事情。如果套用一句经典的评语,我们可以这样总结:一个人做错一道选择题并不难,难的是全部做错,一个不对。不幸的是,这种小概率事件又发生了,而且就在我们身边:事情是这样的——HDU 有个网名叫做 8006 的男性同学,结交网友无数,最近该同学玩起了浪漫,同时给 n 个网友每人写了一封信,这都没什么,要命的是,他竟然把所有的信都装错了信封!注意了,是全部装错哟!现在的问题是:请大家帮可怜的 8006 同学计算一下,一共有多少种可能的错误方式呢?
输入:
输入数据包含多个多个测试实例,每个测试实例占用一行,每行包含一个正整数 n(1<n<=20), n 表示 8006 的网友的人数。
输出:
对于每行输入请输出可能的错误方式的数量,每个实例的输出占用一行。
样例输入:
2
3
样例输出:
1
2
2.基本思路
要把所有的信件全都犯错,在数学上,这是一个排列组合的问题(错排)。我们采用如下的步骤进行分析。
符号定义:
F
(
n
)
F(n)
F(n)表示
n
n
n封信全都放错的情况总数。
有
n
n
n封信
1
,
2
,
.
.
.
,
k
,
.
.
.
,
n
1,2,...,k,...,n
1,2,...,k,...,n,其对应的信封为
L
1
,
L
2
,
.
.
.
,
L
k
,
.
.
.
,
L
n
L_1,L_2,...,L_k,...,L_n
L1,L2,...,Lk,...,Ln
现在考虑将第n封信加入到信件的集合中:
存在以下两种情况:
①将第n封信放在
L
k
L_k
Lk中,且将第k封信放在了
L
n
L_n
Ln中那么此时只需要对剩余的
n
−
2
n-2
n−2封信及其他们刚好对应的
n
−
2
n-2
n−2个信封进行错排即可对应
F
(
n
−
2
)
F(n-2)
F(n−2),又因为第
k
k
k封是在
n
−
1
n-1
n−1封信中挑选出来的有
n
−
1
n-1
n−1种可能性,所以①对应的情况总数为
(
n
−
1
)
∗
F
(
n
−
2
)
(n-1)*F(n-2)
(n−1)∗F(n−2) .
②将第n封信放在
L
k
L_k
Lk中,但第k封信没有放在
L
n
L_n
Ln中。也就是说,第k封信不能放在
L
n
L_n
Ln当中,那么其实在剩余的n-1封信中其实可以把
L
n
L_n
Ln等价为
L
k
L_k
Lk ,因为对于
L
n
L_n
Ln的约束只有第k封信不能放进去,那么对于剩余的n-1封信其实就可以将问题等价转化为有n-1封信:
1
,
2
,
.
.
.
k
,
.
.
.
,
n
−
1
1,2,...k,...,n-1
1,2,...k,...,n−1,和n-1个信封:
L
1
,
L
2
,
.
.
.
,
L
k
,
.
.
.
L
n
−
1
L_1,L_2,...,L_k,...L_{n-1}
L1,L2,...,Lk,...Ln−1。那么对这n-1封信进行错排就会有F(n-1)种情况,和之前一样第
k
k
k封是在
n
−
1
n-1
n−1封信中挑选出来的有
n
−
1
n-1
n−1种可能性,所以②对应的情况总数为
(
n
−
1
)
∗
F
(
n
−
1
)
(n-1)*F(n-1)
(n−1)∗F(n−1) .
所以综上所述:可以等到如下递推方程:
F
(
n
)
=
(
n
−
1
)
∗
F
(
n
−
1
)
+
(
n
−
1
)
∗
F
(
n
−
2
)
F(n)=(n-1)*F(n-1)+(n-1)*F(n-2)
F(n)=(n−1)∗F(n−1)+(n−1)∗F(n−2)
以上就是数学上常说的错排公式。
那么有了如上的递推方程,我们就可以采用递归/迭代的方式进行求解。这里推荐采用迭代的方式,因为如果采用递归的话,子问题F(n-1)和F(n-2)的求解会存在大量重复的子问题,导致大量重复的计算,当递归的层数变深之后效率就会变得十分低下。
还有一个需要注意的细节就是该题中N的取值范围为{N|1≤N≤20,N∈Z},而F(20)不在int类型的范围内,需要采用long long类型才会导致结果不会溢出。
3.代码实现
#include <iostream>
using namespace std;
//F(n)=(n-1)*F(n-1)+(n-1)F(n-2)
long long F_recursive(int n){//采用递归的方式进行求解
if(n==1)return 0;
if(n==2)return 1;
else
return (n-1)*F_recursive(n-1)+(n-1)*F_recursive(n-2);
}
long long F_iterator(int n){//采用迭代的方式进行求解
long long a,b,c;
if(n==1)return 0;
if(n==2)return 1;
else{
a=0,b=1;
for(int i=3;i<=n;i++){
c=(i-1)*(a+b);
a=b;
b=c;
}
return c;
}
}
int main()
{
int N;
while(scanf("%d",&N)!=EOF){
printf("%lld\n",F_iterator(N));
}
return 0;
}
/*
样例输入:
2
3
样例输出:
1
2
*/