大家常常感慨,要做好一件事情真的不容易,确实,失败比成功容易多了!做好“一件”事情尚且不易,若想永远成功而总从不失败,那更是难上加难了,就像花钱总是比挣钱容易的道理一样。话虽这样说,我还是要告诉大家,要想失败到一定程度也是不容易的。比如,我高中的时候,就有一个神奇的女生,在英语考试的时候,竟然把 40 个单项选择题全部做错了!大家都学过概率论,应该知道出现这种情况的概率,所以至今我都觉得这是一件神奇的事情。如果套用一句经典的评语,我们可以这样总结:一个人做错一道选择题并不难,难的是全部做错,一个不对。不幸的是,这种小概率事件又发生了,而且就在我们身边:事情是这样的——HDU 有个网名叫做 8006 的男性同学,结交网友无数,最近该同学玩起了浪漫,同时给 n 个网友每人写了一封信,这都没什么,要命的是,他竟然把所有的信都装错了信封!注意了,是全部装错哟!现在的问题是:请大家帮可怜的 8006 同学计算一下,一共有多少种可能的错误方式呢?
输入:
输入数据包含多个多个测试实例,每个测试实例占用一行,每行包含一个正整数 n(1输出:
对于每行输入请输出可能的错误方式的数量,每个实例的输出占用一行。
样例输入:
2
3
样例输出:
1
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类型才会导致结果不会溢出。
#include
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
*/