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("\n");
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 }

小墨原创



ACM/ICPC算法训练 之 数学很重要-浅谈“排列计数” (DP题-POJ1037)

时间: 2024-07-30 10:17:28

ACM/ICPC算法训练 之 数学很重要-浅谈“排列计数” (DP题-POJ1037)的相关文章

ACM/ICPC算法训练 之 数学很重要—斐波拉契●卢卡斯数列(HNNUOJ 11589)

看到这个标题,貌似很高大上的样子= =,其实这个也是大家熟悉的东西,先给大家科普一下斐波拉契数列. 斐波拉契数列 又称黄金分割数列,指的是这样一个数列:0.1.1.2.3.5.8.13.21.34.…… 在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2,n∈N*) 在现代物理.准晶体结构.化学等领域,斐波纳契数列都有直接的应用,为此,美国数学会从1963起出版了以<斐波纳契数列季刊>为名的一份数学杂志,用于专门刊载这方面的

ACM/ICPC算法训练 之 数学很重要-平面几何(POJ 1269)

题意:给定四点的坐标(x,y),分别确定两直线,求出其交点,若重合or平行则输出相应信息 用四个点的坐标算出直线通式(ax+by+c=0)中的a,b,c,然后利用a,b,c计算出交点坐标(其他公式不够通用= =,比如斜率限制) 我利用两次平行判定重合 公式利用 初高中数学知识或代数知识 在草纸上仔细推导出来= =,让a,b,c为整数,比如可以演算得到a = y2-y1,b = x1-x2这一类公式. 详细Code如下: 1 //给定四点,分别确定两直线,求出其交点,若重合or平行则输出相应信息

ACM/ICPC算法训练 之 数学问题

好歹我是数学专业的学生,还是要写写训练的时候遇到的数学问题滴~~ 在ACM集训的时候在各高校OJ上也遇见过挺多的数学问题,例如大数的处理,素数的各种算法,几何问题,函数问题(单调,周期等性质),甚至是各种数学定理或公式的变形.其实算法本身也属于数学研究的范畴(计算机本就是数学的衍生嘛),诸如众多排序算法,搜索算法也是许多精巧的数学逻辑思维,所以大家学计算机千万别忽视数学这门基础学科啊. 貌似说了好多废话= =||,待小编进入正题,今天在湖大OJ训练赛上看到一道数学题,话不多说,直接上图! ___

ACM/ICPC算法训练 之 BFS-广搜进阶-八数码(经典)(POJ1077+HDU1043)

八数码问题也称为九宫问题.(本想查查历史,结果发现居然没有词条= =,所谓的历史也就不了了之了) 在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同.棋盘上还有一个空格,与空格相邻的棋子可以移到空格中.要求解决的问题是: 给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤. 所谓问题的一个状态就是棋子在棋盘上的一种摆法.棋子移动后,状态就会发生改变.解八数码问题就是找出从初状态到目标状态所经过的一系列中间状态.八数码问题一

ACM/ICPC算法训练 之 BFS-广搜入+队列入门-抓牛(POJ3278)

这一题是练习广度优先搜索很好的例题,在很多广搜教学中经常用到,放在这里供学习搜索算法的孩纸们看看= = 题目大意:一维数轴上,农夫在N点,牛在K点,假定牛不会移动,农夫要找到这头牛只能够进行以下三种移动方法 2*N-跳跃到两倍于自己所在的位置 N+1 -右移一位 N -1 -左移一位 BFS解法解析:按照移动步数依次遍历队首,pop掉,并拓展出下一步所有可走位置并依次压入队列(不懂的孩纸们找度娘,很基础的数据结构)中,在这里为Code方便,我使用STL中的queue. Code如下: 1 //从

ACM/ICPC 算法训练 之 &quot;打表&quot;思路(防超时) ——附加素数筛选法

何为"打表"呢,说得简单点就是: 有时候与其重复运行同样的算法得出答案,还不如直接用算法把这组数据所有可能的答案都枚举出来存到一个足够大的容器中去-例如数组(打表),然后再输入数据的时候,直接遍历容器,检索这个数据是否有题意要求的结果. 举一个几乎所有程序员都知道的简单例子= =: 求素数(POJ 1595)-Prime cuts 这一题大意是给出 多组N(1~1000)和C,让你从N内素数的中间项向外扩展C个素数,比如给出7 1,素数有5个(注意此题出题人坑爹得让1作为"素

ACM/ICPC算法训练 之 分治法入门(画图模拟:POJ 2083)

题意:大致就是要求画出这个有规律的Fractal图形了= = 例如 1 对应 X 2 对应 X  X   X    X  X 这个题是个理解分治法很典型的例子(详情请参见Code) 分治法:不断缩小规模,以致把整个大问题分解为若干个可以直接处理的小问题,一般通过递归调用实现,可以用极简代码完成高复杂的工作,但空间与时间占用也相对较大. 1 //分治法画图 2 //Memory:880K Time:16 Ms 3 #include<iostream> 4 #include<cstring&

《ACM/ICPC 算法训练教程》读书笔记一之数据结构(堆)

书籍简评:<ACM/ICPC 算法训练教程>这本书是余立功主编的,代码来自南京理工大学ACM集训队代码库,所以小编看过之后发现确实很实用,适合集训的时候刷题啊~~,当时是听了集训队final的意见买的,感觉还是不错滴. 相对于其他ACM书籍来说,当然如书名所言,这是一本算法训练书,有着大量的算法实战题目和代码,尽管小编还是发现了些许错误= =,有部分注释的语序习惯也有点不太合我的胃口.实战题目较多是比较水的题,但也正因此才能帮助不少新手入门,个人认为还是一本不错的算法书,当然自学还是需要下不少

《ACM/ICPC 算法训练教程》读书笔记 之 数据结构(线段树详解)

依然延续第一篇读书笔记,这一篇是基于<ACM/ICPC 算法训练教程>上关于线段树的讲解的总结和修改(这本书在线段树这里Error非常多),但是总体来说这本书关于具体算法的讲解和案例都是不错的. 线段树简介 这是一种二叉搜索树,类似于区间树,是一种描述线段的树形数据结构,也是ACMer必学的一种数据结构,主要用于查询对一段数据的处理和存储查询,对时间度的优化也是较为明显的,优化后的时间复杂为O(logN).此外,线段树还可以拓展为点树,ZWK线段树等等,与此类似的还有树状数组等等. 例如:要将