zoukankan      html  css  js  c++  java
  • 《程序设计中的组合数学》——全错位排列

       承接上文,这次以递推的思维,介绍组合学当中一个很经典的问题。

       这个问题最开始由瑞士数学家欧拉提出,原始的问题被叫做“装信封问题”,问题的大意就是:有n封信和n封它们各自对应的信封,如果邮递员想要把每封信都放在不属于这封信的信封,那么请问有多少种排法。(这邮递员真无聊)
      想必这个问题在中学阶段数学的【排列组合】都有过接触,但是我记忆非常深刻的是,老师讲到这个模型,自己找了一下n = 5的情况就停止了,然后让大家把前面的数字序列背下来。今日故地重游不禁觉得老师教的好坑爹,搞学习还是要亲历亲为自主探究。
      虽然这个问题的排列数有一个很长的通式,但是想用计算机编程实现,必须要把它简化成可以一步一步执行的递推式。

         假设b被放到了A当中。

      情况1:a在B中。

          这种情况下,剩下的(n-2)个球的排列就已经和a、b、A、B没有关系,这种情况的排列数也就是n-2时候的【全错位排列数】

      情况2:a不在B中。

          这种情况下,就要完成a,c,d,e……与B,C,D,E......的错位排列。而基于a不在B中的条件,可以把a和B看成【对应】信封,这样就相当于是n-1封信进行【全错位排列】。
     

     以上是b在A中的全错排列数,即f(n-1)+f(n-2)。而b可以放在剩除了B以外的任何一个信封当中,因此得到“装信封问题”中,n封信的全错位排列数的递推式是:                                                     f(n) = (n-1)*[f(n-1) + f(n-2)](n > 2)
      
        有了【全错位排列】这个模型,就可以很轻松的解决以下问题了。(Problem source : hdu 2048)

      

    Problem Description
    HDU 2006'10 ACM contest的颁奖晚会隆重开始了! 为了活跃气氛,组织者举行了一个别开生面、奖品丰厚的抽奖活动,这个活动的具体要求是这样的:
    首先,所有参加晚会的人员都将一张写有自己名字的字条放入抽奖箱中; 然后,待所有字条加入完毕,每人从箱中取一个字条; 最后,如果取得的字条上写的就是自己的名字,那么“恭喜你,中奖了!”
    大家可以想象一下当时的气氛之热烈,毕竟中奖者的奖品是大家梦寐以求的Twins签名照呀!不过,正如所有试图设计的喜剧往往以悲剧结尾,这次抽奖活动最后竟然没有一个人中奖!
    我的神、上帝以及老天爷呀,怎么会这样呢?
    不过,先不要激动,现在问题来了,你能计算一下发生这种情况的概率吗?
    不会算?难道你也想以悲剧结尾?!

      读题可以看到,题目需要输出概率,分子显然是【全错位排列数】,而分母是所有可能情况,即是n的阶乘。



      再看这个题.(Problem source : 2049)

    Problem Description
    国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼进行的丰富一些,司仪临时想出了有一个有意思的节目,叫做"考新郎",具体的操作是这样的:
    首先,给每位新娘打扮得几乎一模一样,并盖上大大的红盖头随机坐成一排; 然后,让各位新郎寻找自己的新娘.每人只准找一个,并且不允许多人找一个. 最后,揭开盖头,如果找错了对象就要当众跪搓衣板...
    看来做新郎也不是容易的事情...
    假设一共有N对新婚夫妇,其中有M个新郎找错了新娘,求发生这种情况一共有多少种可能.

      可以看到这两个题目都是在全错位排列的基础上稍做了一些改动,这个题目需要我们输出所有情况数目。   显然为了完成这件事,先要找出那M个找错的悲催新郎,得到一个组合数,然后再乘以(这里涉及【组合学】里一个简单的分步乘法原理)那M个人的【全错排列数】即可得到答案。而至于得到那个组合数,设计两个循环分别得到分子和分母再相除即可。

        再看一个应用稍微灵活的有关错排的题目。  (Problem source : hdu 2068)

    Problem Description
    今年暑假杭电ACM集训队第一次组成女生队,其中有一队叫RPG,但做为集训队成员之一的野骆驼竟然不知道RPG三个人具体是谁谁。RPG给他机会让他猜猜,第一次猜:R是公主,P是草儿,G是月野兔;第二次猜:R是草儿,P是月野兔,G是公主;第三次猜:R是草儿,P是公主,G是月野兔;......可怜的野骆驼第六次终于把RPG分清楚了。由于RPG的带动,做ACM的女生越来越多,我们的野骆驼想都知道她们,可现在有N多人,他要猜的次数可就多了,为了不为难野骆驼,女生们只要求他答对一半或以上就算过关,请问有多少组答案能使他顺利过关。

      数理分析:这道题乍一看好像和错排没什么关系,因为题目中提到只要答对一半或以上即可,好像和错排沾不上边。但是仔细分析一下会发现联系。我们从答对一半(m,对于奇偶的分析是后话),我们要从n个里面选出m,然后乘以剩下的n-m个元素的全错排,就是答对m个的所有总数,然后m+1,依次计算,便可以得到最终的结果。

      编程实现方面也是比较简单,需要打一个错排的表然后再写有个计算组合数的函数,这里写计算组合数的函数有一个技巧是变量都要用double,否则会出现精度上的错误。另外值得注意的一点是,这里最多有25个元素,错排最多也就是12个元素,所以打错排的表的时候,数组不必开太大。

      代码如下。   

    #include<stdio.h>
    double a[15];
    
    double Con(int m , int n)
    {
    
       double ret = 1 , i;
       for(i = 0;i < m;i++)
              ret *= (n - i)/(m - i);
       return ret;
    }
    void make_list()
     {
         int i;
         a[1] = 0;
         a[2] = 1;
           for(i = 3;i <= 15;i++)
               {
                 a[i] = (i - 1)*(a[i - 1] + a[i - 2]);
                 //printf("%.0lf
    ",a[i]);
               }
     }
    
     int main()
     {
            double ans;
           int n , m ,i;
           make_list();
        while(~scanf("%d",&n) , n)
            {
                 ans = 0;
    
                 if(n%2 == 0)
                      m = n / 2;
                 else
                      m = n / 2 + 1;
    
                for(i = m;i < n;i++)
                     ans += Con(i , n)*a[n - i];
    
                    printf("%.0lf
    ",ans + 1);
    
            }
    
     }


      再看一道用到全错位排列公式的简单题目。(Problem source : 1465)

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

      这是一道很标准很基础的错排题目,只是在编写的时候,对于数据类型——到底用__int64还是double,好像对于不同的题目有不同的限制,不过这是oj的问题了。

      简单的代码如下。

    #include<stdio.h>
    __int64 a[25];
    void make_list()
    {
       a[1] = 0 , a[2] = 1;
         int i;
          for(i = 3;i <= 20;i++)
             a[i] = (i - 1)*(a[i - 2] + a[i - 1]);
    }
    
    int main()
    {
      int n;
      make_list();
      while(scanf("%d",&n) != EOF)
         printf("%I64d
    ",a[n]);
    }


    再来看一道有关错排的简单题目。(Problem source : hdu 4534)

    Problem Description
      吉哥还是那个吉哥   那个江湖人称“叽叽哥”的基哥      每当节日来临,女友众多的叽叽哥总是能从全国各地的女友那里收到各种礼物。   有礼物收到当然值得高兴,但回礼确是件麻烦的事!   无论多麻烦,总不好意思收礼而不回礼,那也不是叽叽哥的风格。      现在,即爱面子又抠门的叽叽哥想出了一个绝妙的好办法:他准备将各个女友送来的礼物合理分配,再回送不同女友,这样就不用再花钱买礼物了!      假设叽叽哥的n个女友每人送他一个礼物(每个人送的礼物都不相同),现在他需要合理安排,再回送每个女友一份礼物,重点是,回送的礼物不能是这个女友之前送他的那个礼物,不然,叽叽哥可就摊上事了,摊上大事了......      现在,叽叽哥想知道总共有多少种满足条件的回送礼物方案呢?

      这道题目在错排的基础上,加入的求余处理。我们可以想象,根据错排的递推式,打表循环几次就可以把__int64给打爆,因此这里题目要求进行求余处理。   在算法实现上,我们可以先求出f[i - 1] + f[i - 2],然后求一步余,然后再乘以(i - 1),再求余,这种分步求余的方式能够进一步的防止数据的溢出。

      代码如下:

    #include<stdio.h>
    __int64 a[105];
    const int MOD = 1000000007;
    void make_list()
      {
         a[1] = 0;
         a[2] = 1;
         int i;
            for(i = 3;i <= 100;i++)
              {
                a[i] = a[i - 1] + a[i - 2];
                 a[i] %= MOD;
                 a[i] *= i - 1;
                 a[i] %= MOD;
              }
      }
    
      int main()
      {
          make_list();
         int n , T;
         scanf("%d",&T);
         while(T--)
             {
                scanf("%d",&n);
                printf("%I64d
    ",a[n]);
             }
      }

      

  • 相关阅读:
    Job for vsftpd.service failed because the control process exited with error code
    Linux 调优方案, 修改最大连接数-ulimit
    vsftpd配置文件详解
    Linux下TCP最大连接数受限问题
    vsftp限制FTP用户只能访问自己的目录
    linux YUM常用 命令
    Linux 系统sudo命令
    部分有关 广告联盟作弊 与反作弊资料收集
    Boosted Tree
    如何将数据转换libsvm格式文件
  • 原文地址:https://www.cnblogs.com/rhythmic/p/5503509.html
Copyright © 2011-2022 走看看