全错位排序

简介

全错位排列被著名数学家欧拉(Leonhard Euler,1707-1783)称为“组合数论的一个妙题”的“装错信封问题”的两个特例。

“装错信封问题”是由当时最有名的数学家约翰·伯努利(Johann Bernoulli,1667-1748)的儿子丹尼尔·伯努利(DanidBernoulli,1700-1782)提出来的,大意如下:
一个人写了n封不同的信及相应的n个不同的信封,他把这n封信都装错了信封,问都装错信封的装法有多少种?

公式证明
n个相异的元素排成一排 a1,a2,,an a 1 , a 2 , … , a n ,则 ai a i 不在第i位的排列数为:
证明:
设1,2,…,n的全排列 t1,t2,,tn t 1 , t 2 , … , t n 的集合为I,而使 ti=i t i = i 的全排列的集合记为Ai(1<=i<=n),
Dn=|I||A1A2An| D n = | I | − | A 1 ∪ A 2 ∪ … ∪ A n |
所以 Dn=n!|A1A2An| D n = n ! − | A 1 ∪ A 2 ∪ … ∪ A n |
注意到 |Ai|=(n1)! | A i | = ( n − 1 ) ! ,
|AiAj|=(n2)! | A i ∩ A j | = ( n − 2 ) ! ,…, |A1A2An|=0!=1 | A 1 ∩ A 2 ∩ … ∩ A n | = 0 ! = 1
容斥原理
Dn=n!|A1A2An|=n!C1n(n1)!+C2n(n2)!C3n(n3)!!++(1)nCnn0!=n!(11/1!+1/2!+(1)n/n!) D n = n ! − | A 1 ∪ A 2 ∪ … ∪ A n | = n ! − C n 1 ( n − 1 ) ! + C n 2 ( n − 2 ) ! − C n 3 ( n − 3 ) ! ! + … + ( − 1 ) n C n n ∗ 0 ! = n ! ( 1 − 1 / 1 ! + 1 / 2 ! − … + ( − 1 ) n / n ! )

应用

1、简单排列

1个元素没有全错位排列,2个元素的全错位排列有1种,3个元素的全错位排列有2种,4个元素的全错位排列有9种,5个元素的全错位排列有44种。
即上述公式,表示全部都错有多少种错法。

2、递推公式

2.1递推公式
用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.2概率和极限
有n封不同的信,和n个信封印上了相应的地址。将这n封信放入n个信封中。
A)求至少有一封信刚好放进正确信封中的概率pn

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

都装错信封的装法:n!(1-1/1!+1/2!-1/3!+…+(-1)^n*1/n!)所以原题解答过程为:
这里写图片描述

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

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

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

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

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

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

Input
输入数据的第一行是一个整数C,表示测试实例的个数,然后是C 行数据,每行包含一个整数n(1 < < <script type="math/tex" id="MathJax-Element-2026"><</script>n <= <= <script type="math/tex" id="MathJax-Element-2027"><=</script>20),表示参加抽奖的人数。
Output
对于每个测试实例,请输出发生这种情况的百分比,每个实例的输出占一行, 结果保留两位小数(四舍五入),具体格式请参照sample output。
Sample Input
1 2
Sample Output
50%

#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++)
    {
        //递推公式f(0)=0;f(1)=0;f(2)=1;f(3)=2;
        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%%\n",100*a[n]/b[n]);
        //输出用printf比cout方便得多    
    }  

    return 0;  
}

4、
Problem Description
今年暑假杭电ACM集训队第一次组成女生队,其中有一队叫RPG,但做为集训队成员之一的野骆驼竟然不知道RPG三个人具体是谁谁。RPG给他机会让他猜猜,第一次猜:R是公主,P是草儿,G是月野兔;第二次猜:R是草儿,P是月野兔,G是公主;第三次猜:R是草儿,P是公主,G是月野兔;……可怜的野骆驼第六次终于把RPG分清楚了。由于RPG的带动,做ACM的女生越来越多,我们的野骆驼想都知道她们,可现在有N多人,他要猜的次数可就多了,为了不为难野骆驼,女生们只要求他答对一半或以上就算过关,请问有多少组答案能使他顺利过关。
Input
输入的数据里有多个case,每个case包括一个n,代表有几个女生,(n<=25), n = 0输入结束。
Sample Input
1
2
0
Sample Output
1
1
思路
从全部中选出一半以上的数目遍历,与选出来的全部正确剩下的全部排错,也就是剩下的对应错排。

#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\n",sum+1);  

    scanf("%d",&m);  
    }     
    return 0;  
}
#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\n",(long long int)(sum+1));//  
    }  
    return 0;  
}  
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值