zoukankan      html  css  js  c++  java
  • 全错位排列

    以前接触过这样的题目,但是现在稍微系统点

    首先看一下百度百科对全错位排列的解释:

    基本简介

    全错位排列:即被著名数学家欧拉(Leonhard Euler,1707-1783)称为组合数论的一个妙题的“装错信封问题”。
    “装错信封问题”是由当时最有名的数学家约翰·伯努利(Johann Bernoulli,1667-1748)的儿子丹尼尔·伯努利(DanidBernoulli,1700-1782)提出来的,大意如下:
    一个人写了n封不同的信及相应的n个不同的信封,他把这n封信都装错了信封,问都装错信封的装法有多少种?

    公式证明

    n个相异的元素排成一排a1,a2,...,an。则ai(i=1,2,...,n)不在第i位的排列数为:
    公式公式
    证明:
    设1,2,...,n的全排列t1,t2,...,tn的集合为I,而使ti=i的全排列的集合记为Ai(1<=i<=n),则Dn=|I|-|A1∪A2∪...∪An|.所以Dn=n!-|A1∪A2∪...∪An|.
    注意到|Ai|=(n-1)!,|Ai∩Aj|=(n-2)!,...,|A1∩A2∩...∩An|=0!=1。
    Dn=n!-|A1∪A2∪...∪An|=n!-C(n,1)(n-1)!+C(n,2)(n-2)!-C(n,3)(n-3)!+...+(-1)^nC(n,n)*0!
    =n!(1-1/1!+1/2!-1/3!+...+(-1)^n*1/n!)(可以举例试试,很好懂)
    应用:
    (1)简单排列
    1个元素没有全错位排列,2个元素的全错位排列有1种,3个元素的全错位排列有2种,4个元素的全错位排列有9种,5个元素的全错位排列有44种。

    递推公式

    瑞士数学家欧拉按一般情况给出了一个递推公式:

    用A、B、C……表示写着n位友人名字的信封,a、b、c……表示n份相应的写好的信纸。把错装的总数为记作f(n)。假设把a错装进B里了,包含着这个错误的一切错装法分两类:
    (1)b装入A里,这时每种错装的其余部分都与A、B、a、b无关,应有f(n-2)种错装法。
    (2)b装入A、B之外的一个信封,这时的装信工作实际是把(除a之外的)(n-1 )份信纸b、c……装入(除B以外的)n-1个信封A、C……,显然这时装错的方法有f(n-1)种。
    总之在a装入B的错误之下,共有错装法f(n-2)+f(n-1)种。a装入C,装入D……的n-2种错误之下,同样都有f(n-2)+f(n-1)种错装法,因此:
    f(n)=(n-1) {f(n-1)+f(n-2)}(代码实现的时候这个递推公式很重要)
    公式可重新写成 f(n)-nf(n-1)=-[f(n-1)-(n-1)f(n-2)] (n>2)
    于是可以得到
    f(n)-nf(n-1)=-[f(n-1)-(n-1)f(n-2)]
    =((-1)^2)[f(n-2)-(n-2)f(n-3)]
    =((-1)^3)[f(n-3)-(n-3)f(n-4)]
    =……
    =[(-1)^(n-2)][f(2)-2f(1)]
    最终得到一个更简单的递推式 f(n)=nf(n-1)+(-1)^(n-2)
    或者等价式 f(n)=nf(n-1)+(-1)^(n) n=2,3,4……
    (2)概率和极限:
    n封不同的信,和n个信封印上了相应的地址。将这n封信放入n个信封中。

     A)求至少有一封信刚好放进正确信封中的概率pn

     B)当n趋向于无穷大时 pn的极限是多少?

    刚看完题目第一印象感觉蛮简单,求至少有一封,那就先求出一封都不对的概率,然后用1减之就可以了。可一封都不对的排列数是多少想了许久也没有想出,回来后请教了不少朋友也没能给出正确答案。最终通过网络提问知道了正确答案。

    原来这是有名的“装错信封问题”,它是由著名数学家约翰·伯努利(Johann Bernoulli,1667-1748)的儿子丹尼尔·伯努利(DanidBernoulli,1700-1782)提出来的,大意如下:

    个人写了n封不同的信及相应的n个不同的信封,他把这n封信都装错了信封,问都装错信封的装法有多少种?

    结论是:n!(1-1/1!+1/2!-1/3!+...+(-1)^n*1/n!)所以原题解答过程为:


    全错位排列











    应用举例:

    神、上帝以及老天爷(组合数学,全错位排列)

    Problem Description
    HDU 2006'10 ACM contest的颁奖晚会隆重开始了!
    为了活跃气氛,组织者举行了一个别开生面、奖品丰厚的抽奖活动,这个活动的具体要求是这样的:

    首先,所有参加晚会的人员都将一张写有自己名字的字条放入抽奖箱中;
    然后,待所有字条加入完毕,每人从箱中取一个字条;
    最后,如果取得的字条上写的就是自己的名字,那么“恭喜你,中奖了!”

    大家可以想象一下当时的气氛之热烈,毕竟中奖者的奖品是大家梦寐以求的Twins签名照呀!不过,正如所有试图设计的喜剧往往以悲剧结尾,这次抽奖活动最后竟然没有一个人中奖!

    我的神、上帝以及老天爷呀,怎么会这样呢?

    不过,先不要激动,现在问题来了,你能计算一下发生这种情况的概率吗?

    不会算?难道你也想以悲剧结尾?!
     

    Input
    输入数据的第一行是一个整数C,表示测试实例的个数,然后是C 行数据,每行包含一个整数n(1<n<=20),表示参加抽奖的人数。

     

    Output
    对于每个测试实例,请输出发生这种情况的百分比,每个实例的输出占一行, 结果保留两位小数(四舍五入),具体格式请参照sample output。

     

    Sample Input
    1 2
     
    Sample Output
    50.00%
     

    一开始我陷入了一个错误的想法:有n个人,第1个人有n-1种取法(不能去自己的),第2个人有n-2种取法(减去去自己的字条和1取得的那张)……事实上,若第1个人取了第2个人的字条。第2个人应有n-1种取法!

    <span style="color:#000000;">#include <iostream>
    using namespace std;
    int main()
    {
    	int c,n,i;
    	//定义为double型就不需使用long long,同时输出不需要转换类型 
    	double b[21]={0,1,2,6,24,120,720},a[21]={0,0,1,2};
    	//计算全错位排列数 
    	for(i=4;i<21;i++)
    	{
    		a[i]=(i-1)*(a[i-1]+a[i-2]);
    	}
    	//计算阶乘 
    	for(i=7;i<21;i++)
    	{
    		b[i]=i*b[i-1];
    	}
    	
    	cin>>c;
    	
    	while(c--)
    	{
    		cin>>n;
    		printf("%.2lf%%
    ",100*a[n]/b[n]);//输出用printf比cout方便得多	
    	}
    	
    	return 0;	
    }
    </span>

    应用2:
    思路:从全部中选出一半以上的数目遍历,与选出来的全部正确剩下的全部排错,也就是剩下的对应错排,很简单,但是开始却出现上一题中的错误理解。。。
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <algorithm>
    #include <cstdlib>
    using namespace std;
    int main (){
       int m;
       __int64 C[30][30],wrongqueue[15]={0,0,1,2};//C[][]为组合,wrongqueue为全错位排列  [0]用不到
       for(int i=1;i<=25;i++){
       	   C[i][1]=i;
       	   C[i][0]=1;
       	   for(int j=2;j<=i;j++){
       	   	C[i][j]=C[i][j-1]*(i-j+1)/j;
       	   } 
    	}
       for(int i=3;i<=12;i++)
          wrongqueue[i]=(i-1)*(wrongqueue[i-1]+wrongqueue[i-2]); 
       scanf("%d",&m);
       while(m!=0){
       	__int64 sum=0;
       	if(m%2==0)
       		for(int i=m/2;i<m;i++)
       			sum=sum+C[m][i]*wrongqueue[m-i];
       	else
       	for(int i=m/2+1;i<m;i++)
       			sum=sum+C[m][i]*wrongqueue[m-i];	
            printf("%I64d
    ",sum+1);
    	scanf("%d",&m);
    	}	
    	return 0;
    } 
    

    另:仔细看来看和我的思路一样,只不过我的在求Cnm的时候打表了,还有他的sum类型是double(其实和long long int 是一样长的位数)
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <iostream>
    #include <math.h>
    using namespace std;
    int a[21];
    double PaC(int n, int m)                   //Permutation and combination.
    {
        double s=1,i;   //小技巧,doulbe型的定义 
        for(i=0;i<m;i++)
            s*=(n-i)/(i+1);
        return s;
    }
    
    
    void init()                           //错排的序列 
    {
        int i;
        a[0]=0;a[1]=0;a[2]=1;
        for(int i=3;i<=21;i++)
          a[i]=(a[i-1]+a[i-2])*(i-1);
        return ;
    }
    
    int main()
    {
        int N,i;
        double sum;
        init();
        while(~scanf("%d",&N),N)
        {
            int m;
            sum=0;
            if(N%2==0)
                m=N/2;
            else
                m=N/2+1;
            for(i=m;i<N;i++)
                sum+=PaC(N, i)*a[N-i];            //Cnm*a[N-m];即从中选出m个正确的,则有n-m个错排的。根据分步计数原理可知。 
            printf("%lld
    ",(long long int)(sum+1));//
        }
        return 0;
    }












    
  • 相关阅读:
    【Gitbook】实用配置及插件介绍
    【Git】学习记录
    【Ubuntu】使用记录
    intellij idea
    【应用】信息短时存储
    leetcode pow(x,n)实现
    SSM框架-----------SpringMVC+Spring+Mybatis框架整合详细教程
    《平凡的世界》之我看
    垃圾收集器与内存分配策略(三)
    垃圾收集器与内存分配策略(二)
  • 原文地址:https://www.cnblogs.com/zswbky/p/5432015.html
Copyright © 2011-2022 走看看