全排列与逆序对

  在百度上搜索“全排列及其逆序数”能找出N多结果,但是内容很分散。本文旨在对这一方面的问题进行总结整理,如有不完整或者错误之处请与本人联系。

  下面是正文。

.

  一. 预备知识

.

  这部分就是百度上一搜一大片的东西,不过还是强调一下。

.

  1. 全排列

    从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫n的全排列。[1]对于n的全排列,共有n!种情况。

  2.  逆序、逆序数和奇、偶排列

    在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。逆序数为偶数的排列称为偶排列;逆序数为奇数的排列称为奇排列。[2]

      例如,对于n=3的全排列:

全排列

123

231

312

132

213

321

逆序数

0

2

2

1

1

3

奇偶性

.

  二. 相关问题

.

  1.   给定一个排列,求它的逆序数。[3]

问题:给定一个排列,求它的逆序数是多少。

    分析:设 p1,p2,…,pn 为n的一个全排列,则其逆序数为t=t1+t2+…+tn=

    其中 ti为排在pi 前,且比pi 大的数的个数。

    这部分代码比较简单,此处略去。

  2.  根据逆序数推排列数。[4]

问题:给定一个n元排列,它的逆序数存在且唯一。那么反过来,已知一个n元排列的逆序数为m,
这样的n元排列有多少种?

  分析:我们用f(n,m)表示逆序数为m的n元排列的个数,则求满足条件的排列个数问题就转化为求f(n,m)的值的问题。

  为了得到最终结果,我们先研究f(n,m)的性质。可以得到以下几个命题:

  [命题1] 对任意n>=20<=m<=C(n,2)f(n,m)>=1;当m>C(n,2),f(n,m)=0

    C(a,b)表示从a个元素中选出b个元素的选择种数

  证明:先证命题1的前半部分。

    (1)n=2时,m只可能是0或1.有f(2,0)=1,f(2,1)=1

    (2)假设n=k时命题成立,即有f(k,m)>=1(0<=m<=C(k,2))

    当n=k+1时,排列1,2,3,…,k+1满足逆序数为0,所以有f(k+1,0)>=1

    若m>=1,考虑某一逆序数为m-1的k元排列a1,a2,…,ak(由归纳假设知这个排列存在).现将k+1插入到a(k-1)和ak之间得到一个k+1元排列a1,a2,…,a(k-1),k+1,ak,这个排列的逆序数为m,所以f(k+1,m)>=1

    综上所述,对所有的0<=m<=C(n,2)有f(n,m)>=1

    再证命题的后半部分(比较显然)。

    因为对于一n元排列,若要使其逆序数达到最大,那么排列中任两个数都构成逆序,其逆序数为C(n,2),所以不可能存在一n元排列使其逆序数大于C(n,2)

  [命题2] f(n,m)=f(n,C(n,2)-m)

    证明:对于一个逆序数为m的n元排列a1,a2,…,an,n元排列an,a(n-1),…,a2,a1的逆序数为C(n,2)-m.同理,对于一个逆序数为C(n,2)-m的n元排列a1,a2,…,an,n元排列an,a(n-1),…,a2,a1的逆序数为m.

    于是,逆序数为m的n元排列的集合 和 逆序数为C(n,2)-m的n元排列的集合 是一一对应关系,所以f(n,m)=f(n,C(n,2)-m)

  [命题3] f(n+1,m)=f(n,m)+f(n,m-1)++f(n,m-n)

    先考虑由1,2,…,n组成的一个排列a1,a2,…,an,由于任一个由1,2,…,n,n+1构成的排列,可以看成将n+1加入的a1左边,或an的右边,或ai与a(i+1)之间(1<=i<=n-1)而形成的。又由于在n+1加入任一个位置之后,其前面没有比n+1大的数,其后面的数都比n+1小,而且加入n+1之后,使得对后面的每一个数而言,其前面比自身大的数的个数增加1,对n+1前面的数而言,其前面比自身大的个数不变。于是当选取n+1加入a1的左边时,排列a1,a2,…,an的逆序数变为m+n,当n+1加入ai与a(i+1)之间(1<=i<=n-1)时,排列a1,a2,…,an的逆序数变为m+n-i,当加入an的右边时,排列a1,a2,…,an的逆序数仍为m。

    于是,对于任一逆序数为m的n+1元排列,考虑n+1所处的位置。若n+1在第k位,则其余n个元素构成逆序数为m-n+k-1的n元排列,且对任一逆序数为m-n+k-1的n元排列而言,当n+1放在该排列的某位,使得在新排列中n+1处于第k位,那么新排列成为一逆序数为m的n+1元排列。

    于是,逆序数为m的n+1元排列的集合 和 逆序数为m的n元排列的集合 是一一对应关系。这样,对于一个n+1元排列,f(n,m)表示n+1排在最后的排列个数,f(n,m-1)表示n+1排在倒数第二位的排列的个数……,f(n,m-n)表示n+1排在首位的排列个数,且f(n+1,m)为这些排列之和。

    所以f(n+1,m)=f(n,m)+f(n,m-1)+…+f(n,m-n)(注:记b<0时,f(a,b)=0)

  [命题4] f(n,0)=f(n,C(n,2))=1

  [命题5] f(n,1)=f(n,C(n,2)-1)=n-1(n>1)

    证明:由命题3和命题4有

       f(n,1)=f(n-1,1)+f(n-1,0)=f(n-1,1)+1

    反复应用上式,根据f(2,1)=1,得到命题5成立

  [命题6] f(n,2)=f(n,C(n,2)-2)=C(n,2)-1(n>2)

    证明:由命题3,4,5有

      f(n,2)=f(n-1,2)+f(n-1,1)+f(n-1,0)=f(n-1,2)+n-1

    反复应用上式,根据f(2,2)=0,得到命题6成立

    同理,可证明

      f(n,3)=C(n,3)-C(n,2)-C(n,1) (n>3)

      f(n,4)=C(n,4)+2C(n,3)-C(n,1) (n>4)

      f(n,5)=C(n,5)+3C(n,4)+2C(n,3)-C(n,2)+1 (n>5)

      …

    我们可以无限递推下去,计算f(n,k)的时候要用到前面所推导出的f(n,1)~f(n,k-1)。但是却很难得到一个通项公式。这个问题就类似于用∑(k^t-(k-1)^t) 来求∑(k^(t-1)),每作下一项都要用到前面所有的结果。

    由于在命题3中我们得出了一个关于f(n,m)的递推公式,而且又知道了一些初始值。因此可以设计一个程序,输入n,m,输出f(n,m)。[5]

.  

  参考代码:

program code1;
  var
    a:array[1..500,1..500]of longint;
    b,c:longint;
    i,j,m,n,k:longint;
  begin
    a[2,0]:=1;
    a[2,1]:=1;
    for i:=3 to 500 do
      for j:=0 to 499 do
        begin
          b:=j-i;
          if b<0 then b:=0
            else b:=j-i+1;
          for k:=n to j do
            c:=c+a[i-1,k];
          a[i,j]:=c;
          c:=0;
        end;
    readln(n,m);
    writeln(a[n,m]);
  end.
.

  3.  根据每个数的逆序数,然后求出原排列。[6]

问题:对于一个集合S={1,2,3,...N}的任一排列a1、a2、a3、... aN,我们定义ai的逆序数为
∑(aj>ai | j<i),即排在ai前的所有比ai大的数的个数。我们把每个数的逆序数按下标排出就构
成排列a1、a2、a3、... aN的一个逆序排列。比如排列 3 1 2 的逆序排列为 1 1 0(从左往右
依次是1的逆序数、2的逆序数、3的逆序数)。现在我们给出一个排列的逆序排列,请求出原排列。[7]

  分析:<developing…一种题解请参阅引用7>

.

  4.  给定逆序数,求满足此逆序数的最小排列。[8]

问题:对于n的全排列,给定一个正整数m,找到满足逆序数是m的最小的排列。例如,n=5,m=4,此
满足逆序数为4最小的排列是1 3 5 4 2

  分析:解决此问题,要先了解以下几个命题和定理。(注意,在证明中排列的大于、小于指两排列在全排列中位置的前后关系,小于即在前,大于反之)

    [定理1]对于n的全排列,在它完全倒序的时候(也就是n,n-1,…,2,1的时候)逆序数最多。 

      证明:此处省略,可用反证法简单证明。

    [命题1]对于一个形如1,2,3,…,i-1,i,n,…i+1的排列q(如n=5时的1,2,5,4,3),即在数n前保证首项为1且严格以公差为1递增而数n之后排列任意的数列, 

        (1)当数n之后是递减的时候q的逆序数最多,为t=C(n-i,2) 

        (2)排列q是出现逆序数为t的最小排列。 

  证明:首先证明(1)。

   这个证明很容易。因为数列的前i+1项已经确定,所以整个数列的逆序数只和i+2项到n项有关。显然,由定理1,当第i+2项到第n项完全倒序的时候逆序数最大。比如说排列1,2,5,4,3就比1,2,5,3,4构成的逆序对多。

   现在证明(2)。

   假设存在一个排列p的逆序数为m且p小于q。我们知道,q的前i位已经是数n在当前位置的最小排列。也就是说,在q中无论是n还是n之后的任意一个数,将它与q中的1到i位的任意一个数更换位置后得到的排列一定大于q。比如,对于排列q:1,2,5,4,3,前两个数1、2的位置是不能变的,否则会大于q,比如1,3,5,4,2。

   所以我们只能从q的第i+1位到n位入手。由(1)可知,当n在第i+1位时,无论后面的数怎么排列,逆序数都无法超过m。所以p的数n一定在第i+2到第n位的任一位上。

   好的,我们假设数n在第i+2位上。在这种情况下不难发现,我们把原来n后面最大的数填补到第i+1位,这时的逆序数最多。比如对于1,2,5,4,3,我们把5移动到第4位,然后将4填补到第3位,排列就变成了1,2,4,5,3,显然这要比1,2,3,5,4优。

   我们分析一下这时p的逆序数。因为数n到了第i+2位,则由它构成的逆序数为n-i-2。同样,由第i+1位(也就是例子中的4,即n-1)构成的逆序数也是n-i-2。显然,由于保持了递减的特征,剩下的第i+3到第n位的逆序数为C(n-i-2,2)。(这里姑且认为C(1,2)=1)这时总的逆序数为

          t1 =C(n-i-2,2)+2*(n-i-2)

            =(n-i-2)(n-i-3)/2+2*(n-i-2)

            =(n-i-2)(n-i+1)/2.

     而q的逆序数

          t2=C(n-i,2)

             =(n-i)(n-i-1)/2.

   用t2-t1,得到定值1。也就是说,无论n取何值,t2总是大于t1

   如果再将数n向右移动一位,这时我们只有保持前1到i+1位(即例子中的1,2,3,4)不变才能够保证之后的变换能够使逆序数最大(这个很显然,不特别证明)。然而,当前面的i+1位确定了以后,怎么变换后面的数字才能够使总逆序数最大呢?还是要原来n后面最大的数填补到第i+1位。我们已经证明,这样做是不如不换优的。所以,不存在一个小于q的排列p使p的逆序数为m。证毕。

      [命题2]在命题1所设定的排列q的基础上,我们将数n后面的第k小数与数n的前一个数(即i)交换,然后使数n后面保持逆序。这样得到的新排列所含的逆序数为t=C(n-i,2)+k,且这个排列是逆序数为t的最小排列。 

        证明:首先,在此强调一下排列q的结构,如图一:

   由于操作以后数n之后仍然保持逆序,所以从第i+1到第n位的逆序数仍为C(n-i,2)。对于x来说,交换之前后面有k-1个比它小的数,交换之后又加上了一个,所以x所拥有的逆序数(即以x为左的逆序数)为k-1+1=k,此时总的逆序数为C(n-i,2)+k。

   对于新排列逆序数为t=C(n-i,2)+k的最小排列的证明与命题1的证明相似,在此处不加以详细证明。

   到此我们可以得出,命题2也是真命题。

   有了这两个命题作基础,我们很容易就能够得到这道题的算法。首先利用命题1,二分查找找到数n的位置,我们可以得到数n在这个位置的时候形如q的排列的逆序数t。然后计算t与m的差值,得出k,然后根据命题2进行变换,最后得出要求的排列。

.

   参考代码:

program code2;
  var
    m,n,p:int64;
    i,j:longint;
  function find(x:longint):longint;
    var
      l,r,mid,k1,k2:int64;
    begin
      l:=1;
      r:=n;
      while l<r do
        begin
          mid:=(l+r)>>1;
          k1:=((mid-1)*mid)div 2;
          k2:=(mid*(mid+1))div 2;
          if (x<k2)and(x>=k1)then
            exit(mid)
            else begin
                   if x>k1 then l:=mid+1;
                   if x<k1 then r:=mid;
                 end;
        end;
      if l=r then exit(l);
    end;
  procedure make;
    var
      k:int64;
      i,j:longint;
      v:array[1..50000]of boolean;
    begin
      fillchar(v,sizeof(v),false);
      k:=m-((p*(p-1))div 2);
      for i:=1 to n-p-1 do
        begin
          write(i,' ');
          v[i]:=true;
        end;
      v[n-p+k]:=true;
      v[n]:=true;
      if n-p+k>0 then write(n-p+k,' ');
      write(n,' ');
      for i:=n downto 1 do
        if not v[i] then write(i,' ');
    end;
  begin
    readln(n,m);
    p:=find(m);
    make;
  end.

.

  本文介绍了有关全排列和逆序对的4个问题,层次有限而且欠全面。希望已经有的部分对你能够有所帮助,如果有任何纰漏,请尽快回复本文或者与我联系,谢谢!

.

引用来源:

[1]:百度百科 词条:全排列

[2]:百度百科 词条:逆序数 

[3]:题目来源:经典问题

[4]:题目来源:经典问题

[5]:charlesgao数学博客 http://www.charlesgao.com/?p=19

[6]:题目来源:boj1275构造原排列

[7]:joaquin的百度空间 http://hi.baidu.com/joaquin_shj/blog/item/77961aea310d09d6d539c988.html

[8]:题目来源:POJ某题(寻找中)/某模拟赛-末日传说

 

扩展阅读:

[1]:HDU 1394 http://acm.hdu.edu.cn/showproblem.php?pid=1394

  相关题解:http://blog.163.com/prevBlogPerma.do?host=lyt9469@126&srl=170442358201122002733316&mode=prev

[2]:POJ 2828 http://poj.org/problem?id=2828

[3]:BOJ 1197

 

本文地址:http://www.cnblogs.com/saltless/archive/2011/06/01/2065619.html

(saltless原创,转载请注明出处)

posted on 2011-06-01 11:55  saltless  阅读(7102)  评论(0编辑  收藏  举报

导航