不容易系列之一(九度教程第 94 题)

不容易系列之一(九度教程第 94 题)

时间限制:1 秒 内存限制:128 兆 特殊判题:否

1.题目描述:

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

输入:
输入数据包含多个多个测试实例,每个测试实例占用一行,每行包含一个正整数 n(1 输出:
对于每行输入请输出可能的错误方式的数量,每个实例的输出占用一行。

样例输入:
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 n2封信及其他们刚好对应的 n − 2 n-2 n2个信封进行错排即可对应 F ( n − 2 ) F(n-2) F(n2),又因为第 k k k封是在 n − 1 n-1 n1封信中挑选出来的有 n − 1 n-1 n1种可能性,所以①对应的情况总数为 ( n − 1 ) ∗ F ( n − 2 ) (n-1)*F(n-2) (n1)F(n2) .
将第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,...,n1,和n-1个信封: L 1 , L 2 , . . . , L k , . . . L n − 1 L_1,L_2,...,L_k,...L_{n-1} L1,L2,...,Lk,...Ln1。那么对这n-1封信进行错排就会有F(n-1)种情况,和之前一样第 k k k封是在 n − 1 n-1 n1封信中挑选出来的有 n − 1 n-1 n1种可能性,所以②对应的情况总数为 ( n − 1 ) ∗ F ( n − 1 ) (n-1)*F(n-1) (n1)F(n1) .
所以综上所述:可以等到如下递推方程:
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)=(n1)F(n1)+(n1)F(n2)
以上就是数学上常说的错排公式。
那么有了如上的递推方程,我们就可以采用递归/迭代的方式进行求解。这里推荐采用迭代的方式,因为如果采用递归的话,子问题F(n-1)和F(n-2)的求解会存在大量重复的子问题,导致大量重复的计算,当递归的层数变深之后效率就会变得十分低下。
还有一个需要注意的细节就是该题中N的取值范围为{N|1≤N≤20,N∈Z},而F(20)不在int类型的范围内,需要采用long long类型才会导致结果不会溢出。

3.代码实现

#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
*/


你可能感兴趣的:(算法刷题集)