• ACM/ICPC 之 DP-浅谈“排列计数” (POJ1037)


      这一题是最近在看Coursera的《算法与设计》的公开课时看到的一道较难的DP例题,之所以写下来,一方面是因为DP的状态我想了很久才想明白,所以借此记录,另一方面是看到这一题有运用到 排列计数 的方法,虽然排列计数的思路简单,但却是算法中一个数学优化的点睛之笔。

      


      Poj1037  A decorative fence

      题意:有K组数据(1~100),每组数据给出总木棒数N(1~20)和一个排列数C(64位整型范围内),N个木棒长度各异,按照以下条件排列,并将所有可能结果进行字典序排序
      1.每一个木棒两侧木棒的长度都比该木棒或者(除该木棒在两端处外)

      2.木棒由小到大进行排序,完成1中排列后得到的排列即为结果,将所有结果进行字典序排序。

      

      现在求总木棒数为N时,排列数为C的结果。

      大致思路:利用动态规划构造排列状态打出1~20的排列数表,然后根据排列计数的原理找到排列数为C的排列用数组存储并输出。

        构造三维DP数组:dp[n][i][2]-n木棒下,最新插入的第 i 短木棒的可能方案数

            数组第三维具体表述:dp[n][i][DOWN]:第 i 短木棒以下降状态插入 |  dp[n][i][UP]:第 i 短木棒以上升状态插入

        构建三维DP的状态转移方程   dp[n][i][UP] = ∑(dp[n-1][k][DOWN]) (k = 1,2...i-1)  //所有n-1木棒时的下降状态之和-得到n木棒时的上升状态DP值

                      dp[n][i][DOWN] = ∑(dp[n-1][k][UP]) (k = i,i+1...n-1)   //所有n-1木棒时的上升状态之和-得到n木棒时的下降状态DP值


      排列计数:

          这一题中如果我们已经知道n个木棒的排列数,我们应该如何去求第C个排列的状态呢?

        难道我们要列出所有的排列状态,然后排序后去找吗,显然这是一种很愚蠢的做法,不仅代码冗长,而且耗时较长,所以这里需要我们进行查找排列数的优化。

       例子:

        举个例子,如果我们知道1!,2!,3!,4!...的值,现在求1~5的全排列中第41个排列数是多少该怎么求呢?

        其实我们可以简单想想如果第一位数是1的话,后面还有2~5总计4个数的全排列,因此首位是1的排列数有4! = 24种方案,24<41,因此首位一定不是1,

        现在首位为1的情况要排除掉,我们首位从2开始,现在剩余要找到的排列数是41-24 = 17了,而首位为2的排列数也是24种,24>17,因此首位一定是2了,

        首位确定了,我们就可以找第二位了,首先从1开始,后面还有三位数排列,因此排列数共3! = 6,6<17,因此第二位一定不是1了,

        所以我们第二位从没有确定的3开始,现在要找寻的排列数是17-6=11....

       以此类推,我们就可以找到第41种排列情况是24513,这样的最坏时间度为O(n2)

     

       那么这一题也可以采用类似的简单排列计数算法

       最终 Code 如下:

        

     1 //Memory:180K Time:0 Ms
     2 #include<iostream>
     3 #include<cstdio>
     4 #include<cstring>
     5 using namespace std;
     6 
     7 #define MAX 21
     8 
     9 enum State{
    10     DOWN,    //下降状态
    11     UP,        //上升状态
    12 };
    13 
    14 __int64 dp[MAX][MAX][2];    //所有状态
    15 int permut[MAX];    //答案排列-permutation
    16 int v[MAX];
    17 
    18 void DP(int n)    //初始DP-dp[i][j][]-为bar共 i 个时,最新insert木棒 j 的总情况数
    19 {
    20     dp[1][1][DOWN] = dp[1][1][UP] = 1;
    21     for (int i = 2; i <= n; i++)    //现有bar数
    22         for (int j = 1; j <= i; j++)    //最新insert的bar M (第j短)
    23         {
    24         for (int k = j; k < i; k++)        //+all可达此上升态的上一个状态(下降)DP值(k >= j)
    25             dp[i][j][UP] += dp[i - 1][k][DOWN];
    26         for (int k = 1; k < j; k++)        //+all可达此下降态的上一个状态(上升)DP值(k < j)
    27             dp[i][j][DOWN] += dp[i - 1][k][UP];
    28         }
    29     return;
    30 }
    31 
    32 void Find_permutation(int n, __int64 c)
    33 {
    34     memset(v, 0, sizeof(v));
    35     memset(permut, 0, sizeof(permut));
    36     for (int i = 1; i <= n; i++)
    37     {
    38         __int64 skip = 0;    //跳过方案数
    39         int No = 0;
    40         for (int cur = 1; cur <= n; cur++)    //第cur短的bar
    41         {
    42             if (!v[cur])
    43             {
    44                 No++;    //cur在剩余木棒中第No短
    45                 if (i == 1)
    46                     skip = dp[n][No][UP] + dp[n][No][DOWN];    //No==1
    47                 else
    48                 {
    49                     //题意条件+排列计数知识
    50                     if (cur > permut[i - 1] && (i == 2 || permut[i - 2] > permut[i - 1]))
    51                         skip = dp[n-i+1][No][DOWN];    //前一所有下降状态-达到当前上升状态
    52                     else if (cur < permut[i - 1] && (i == 2 || permut[i - 2] < permut[i - 1]))
    53                         skip = dp[n-i+1][No][UP];    //前一所有上升状态-达到当前下降状态
    54                 }
    55                 if (skip >= c)
    56                 {
    57                     v[cur] = 1;
    58                     permut[i] = cur;
    59                     break;
    60                 }
    61                 else
    62                     c -= skip;
    63             }
    64         }
    65     }
    66     /* PRINT */
    67     for (int i = 1; i <= n; i++)
    68         printf("%d ", permut[i]);
    69     printf("
    ");
    70 }
    71 
    72 int main()
    73 {
    74     int T, n;
    75     __int64 c;
    76 
    77     DP(20);
    78 
    79     scanf("%d", &T);
    80     while (T--)
    81     {
    82         scanf("%d%I64d", &n, &c);
    83 
    84         Find_permutation(n, c);
    85     }
    86 
    87     return 0;
    88 }
    小墨原创

    他坐在湖边,望向天空,她坐在对岸,盯着湖面
  • 相关阅读:
    Java 多态
    Java 继承与抽象类
    Java 接口
    关于Oracle数据库故障诊断基础架构
    监控性能
    监视错误和警报
    内存管理参考
    使用自动内存管理
    内存架构概述
    关于内存管理
  • 原文地址:https://www.cnblogs.com/Inkblots/p/4789886.html
Copyright © 2020-2023  润新知