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 如下:
//Memory:180K Time:0 Ms
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std; #define MAX 21 enum State{
DOWN, //下降状态
UP, //上升状态
}; __int64 dp[MAX][MAX][]; //所有状态
int permut[MAX]; //答案排列-permutation
int v[MAX]; void DP(int n) //初始DP-dp[i][j][]-为bar共 i 个时,最新insert木棒 j 的总情况数
{
dp[][][DOWN] = dp[][][UP] = ;
for (int i = ; i <= n; i++) //现有bar数
for (int j = ; j <= i; j++) //最新insert的bar M (第j短)
{
for (int k = j; k < i; k++) //+all可达此上升态的上一个状态(下降)DP值(k >= j)
dp[i][j][UP] += dp[i - ][k][DOWN];
for (int k = ; k < j; k++) //+all可达此下降态的上一个状态(上升)DP值(k < j)
dp[i][j][DOWN] += dp[i - ][k][UP];
}
return;
} void Find_permutation(int n, __int64 c)
{
memset(v, , sizeof(v));
memset(permut, , sizeof(permut));
for (int i = ; i <= n; i++)
{
__int64 skip = ; //跳过方案数
int No = ;
for (int cur = ; cur <= n; cur++) //第cur短的bar
{
if (!v[cur])
{
No++; //cur在剩余木棒中第No短
if (i == )
skip = dp[n][No][UP] + dp[n][No][DOWN]; //No==1
else
{
//题意条件+排列计数知识
if (cur > permut[i - ] && (i == || permut[i - ] > permut[i - ]))
skip = dp[n-i+][No][DOWN]; //前一所有下降状态-达到当前上升状态
else if (cur < permut[i - ] && (i == || permut[i - ] < permut[i - ]))
skip = dp[n-i+][No][UP]; //前一所有上升状态-达到当前下降状态
}
if (skip >= c)
{
v[cur] = ;
permut[i] = cur;
break;
}
else
c -= skip;
}
}
}
/* PRINT */
for (int i = ; i <= n; i++)
printf("%d ", permut[i]);
printf("\n");
} int main()
{
int T, n;
__int64 c; DP(); scanf("%d", &T);
while (T--)
{
scanf("%d%I64d", &n, &c); Find_permutation(n, c);
} return ;
}
小墨原创
ACM/ICPC 之 DP-浅谈“排列计数” (POJ1037)的更多相关文章
- ACM/ICPC 之 DP解有规律的最短路问题(POJ3377)
//POJ3377 //DP解法-解有规律的最短路问题 //Time:1157Ms Memory:12440K #include<iostream> #include<cstring ...
- ACM/ICPC 之 最长公共子序列计数及其回溯算法(51Nod-1006(最长公共子序列))
这道题被51Nod定为基础题(这要求有点高啊),我感觉应该可以算作一级或者二级题目,主要原因不是动态规划的状态转移方程的问题,而是需要理解最后的回溯算法. 题目大意:找到两个字符串中最长的子序列,子序 ...
- ACM/ICPC 之 DP进阶(51Nod-1371(填数字))
原题链接:填数字 顺便推荐一下,偶然看到这个OJ,发现社区运营做得很赞,而且交互和编译环境都很赞(可以编译包括Python,Ruby,Js在内的脚本语言,也可以编译新标准的C/C++11,甚至包括Go ...
- 数位dp浅谈(hdu3555)
数位dp简介: 数位dp常用于求区间内某些特殊(常关于数字各个数位上的值)数字(比如要求数字含62,49): 常用解法: 数位dp常用记忆化搜索或递推来实现: 由于记忆化搜索比较好写再加上博主比较蒟, ...
- 2016 ACM/ICPC Asia Regional Shenyang Online 1009/HDU 5900 区间dp
QSC and Master Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others) ...
- 2016 ACM/ICPC Asia Regional Shenyang Online 1007/HDU 5898 数位dp
odd-even number Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)T ...
- BZOJ4517 Sdoi2016 排列计数 【DP+组合计数】*
BZOJ4517 Sdoi2016 排列计数 Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 ...
- [ZJOI2010]排列计数 (组合计数/dp)
[ZJOI2010]排列计数 题目描述 称一个1,2,...,N的排列P1,P2...,Pn是Magic的,当且仅当2<=i<=N时,Pi>Pi/2. 计算1,2,...N的排列中有 ...
- HDU 5000 2014 ACM/ICPC Asia Regional Anshan Online DP
Clone Time Limit : 2000/1000ms (Java/Other) Memory Limit : 65536/65536K (Java/Other) Total Submiss ...
随机推荐
- linux下代替system的基于管道的popen和pclose函数
linux下使用system需要谨慎,那么代替它的方法是什么呢? 标准I/O函数库提供了popen函数,它启动另外一个进程去执行一个shell命令行. 这里我们称调用popen的进程为父进程,由pop ...
- 了不起的Node.js读书笔记
原文摘自我的前端博客,欢迎大家来访问 http://www.hacke2.cn 第二章 Js概览 基于GoogleV8引擎 Object.keys(o) 数组方法:遍历forEach.过滤filter ...
- lustre文件系统部署流程
# 1 准备工作### 1.1 添加以太网址添加以太网地址,使得gio017可以访问到需要安装的节点.修改gio017上的/etc/hosts,将需要批量操作的节点名以如下方式添加.```[gio01 ...
- 清北国庆day1 (脑)残
(留坑) /* 不知道为什要找的循环节TM这么长 */ #include<cstdio> #include<cstdlib> #include<cstring> u ...
- resin
http://blog.csdn.net/sea0x/article/details/6097531 resin 启动: resin 配置文件摘取: <server-default> &l ...
- JSON 问题
{"statusCode":"300","message":"栏目插入出现故障==bannerInfoService.add 栏目 ...
- Javascript高级程序设计——客户端检测
ECMAScript虽然是Javascript的核心,但是要在web中使用Javascript,那么BOM才是核心,BOM为我们提供了操作访问浏览器对象的借口, 但是由于BOM没有标准规范,导致存在不 ...
- HDU 5592
原题: http://acm.hdu.edu.cn/showproblem.php?pid=5592 线段树的变形,先说思路. 题目中给出了当前节点之前的逆序对数,则p[i]-p[i-1]就是对于p[ ...
- 联合主键用Hibernate注解映射的三种方式
第一.将联合主键的字段单独放在一个类中,该类需要实现java.io.Serializable接口并重写equals和hascode,再将该类注解为@Embeddable,最后在主类中(该类不包含联合主 ...
- 汉诺塔(河内塔)算法 ----C语言递归实现
汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具.大梵天创造世界的时候做了三根金刚石柱子, 在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘.大梵天命令婆罗门把圆盘从下面开始按大小顺 ...