题目大意

给定n个数字,规定一种 cute 排序:序列中的数字大小为严格的波浪形,即 a[0] > a[1] < a[2] > a[3] < .... 或者 a[0] < a[1] > a[2] < a[3] .....。对于N个数字来说,可以构成多个cute序列,这些序列按照字典序进行排序,求出第k个序列。

题目分析

一、求字典序的第i个排列

直接一位一位枚举答案!从前到后枚举求得每一位:枚举一位时,计算在这样的前缀下,后面的总的排列数。如果严格小于总编号,则该位偏小,换更大的数,同时更新总编号;若大于等于,则该位恰好,枚举下一位,总编号不用更新。

二、使用动态规划

由于题目要求按照字典序的第k个cute序列,因此我们需要在字典序中,n个数字构成的cute序列以第i大为开头的有多少个。这样一个计数问题,有子结构 + 无后效性(需要进一步证明), 因此考虑使用动态规划。
    一般使用动态规划来解决问题需要问题满足几个条件: 
(1)可以划分子问题,子问题与总问题相似 
(2)无后效性 
    由n个数字构成的cute序列(波浪形序列)中,其连续的n-1个数字肯定也是cute序列; 
    无后效性,在设计状态,并用动归数组dp表示状态、推演状态的时候,需要保证当前点以后的状态只和当前点的状态有关,而与当前点是如何到达(未来的状态只和当前点的当前数值有关,和过去到当前点的路径的无关)。

首先考虑 A[n] 表示n个数字构成的cute序列的总数,显然太粗糙,不知道n个数字之间的关系,无法进行状态推演; 
    然后考虑 A[n][i] 表示由n个数字构成的,且以n个数字中第i大为开头的cute序列的总数,这样来进行状态推演的时候,A[n][i] = sum-of(A[n-1][k]),选择哪些k,和i和k的大小关系有关,因此不能保证无后效性; 
    因此考虑使用 Up[n][i] 表示n个数字构成的,且以第i大为首的上升序列(a[1] > a[0])的个数;Down[n][i]表示n个数字构成的,以第i大为首的下降序列(a[1] < a[0])的个数,这样,就有递推关系:

    for (int k = i; k <= m - 1; k++){
Up[m][i] += Down[m - 1][k];
}
for (int k = 1; k < i; k++){
Down[m][i] += Up[m - 1][k];
}

实现 (c++)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
#define MAX_COL_NUM 22
long long int Up[MAX_COL_NUM][MAX_COL_NUM];
long long int Down[MAX_COL_NUM][MAX_COL_NUM]; int main(){
int T, N;
long long int C;
scanf("%d", &T); //用动态规划,先求出dp数组。
//Up表示开始是上升(即A[1] > A[0]) 的波浪数组, Down表示开始是下降的波浪数组
//Up[n][i] 表示有n个数组成的序列,将第i大的数作为第一位的上升序列的个数
//Down[n][i] 表示由n个数组成的序列,将第i大的数作为第一位的下降序列个数
memset(Up, 0, sizeof(Up));
memset(Down, 0, sizeof(Down)); Up[1][1] = 1;
Down[1][1] = 1;
for (int m = 1; m <= MAX_COL_NUM - 1; m++){
for (int i = 1; i <= m; i++){
for (int k = i; k <= m - 1; k++){
Up[m][i] += Down[m - 1][k];
}
for (int k = 1; k < i; k++){
Down[m][i] += Up[m - 1][k];
}
}
} while (T--){
scanf("%d %llu", &N, &C); //候选序号,存放在vector中,便于删除
vector<int> candidates;
candidates.push_back(0);
for (int m = 1; m <= N; m++){
candidates.push_back(m);
} int result[MAX_COL_NUM]; //存放最后求出的序列
int n = N;
long long int left = C; //字典序第k大的序列
int next_dir = 2; //下一次选用的首数字和第二个数字构成上升还是下降序列,由之前序列的趋势决定
//0, 下降; 1上升; 2 both
//开始设为2,表示总序列的第一个和第二个之间的关系不明确
while (n >= 1){
int k = 1;
//n 表示,此次循环是在n个数中选择
//k 表示,此次选择n个数的第k大(这n个数放在 vector candidate中)去构成序列
while (k <= n){
if (next_dir == 0 && candidates[k] > result[N-n-1]){
if (left > Down[n][k]){
left -= Down[n][k];
}
else{
break;
}
} if (next_dir == 1 && candidates[k] < result[N-n-1]){
if (left > Up[n][k]){
left -= Up[n][k];
}
else{
break;
}
} if (next_dir == 2){
if (left > (Up[n][k] + Down[n][k])){
left -= (Up[n][k] + Down[n][k]);
}
else{
break;
}
}
k++;
}
if (k > n)
k = n;
result[N - n] = candidates[k]; next_dir = ! next_dir; //波浪形数组,方向取反 //当选择出来第一个数字之后,可以根据 left (剩余的序号)以及 Down[n][k](以选择出来的数字为开头的下降序列的个数 ) 决定
//如果 剩余的序号 小于等于 以选择出来的数字为开头的下降序列总数,则说明 第一个数字和第二个数字为下降,之后的next_dir 为上升
//否则,为下降
if (n == N){
if (left <= Down[n][k])
next_dir = 1;
else{
left -= Down[n][k];
next_dir = 0;
} } //从候选数组中删除已经选择出来的那个数
candidates.erase(candidates.begin() + k);
n --;
}
for (int i = 0; i < N; i++){
printf("%d ", result[i]);
}
printf("\n");
}
return 0;
}

poj_1037 动态规划+字典序第k大的更多相关文章

  1. SPOJ Lexicographical Substring Search 求字典序第k大子串 后缀自动机

    题目传送门 思路:按字典序,小的字符优先选取.对于一个字符,如果以这个字符开头的子串大于等于k个,那说明这个字符是应该选的,并且选完之后,可能还要继续选.如果以这个字符开头的子串小于k个,说明这个字符 ...

  2. 前k大金币(动态规划,递推)

    /* ///题解写的很认真,如果您觉得还行的话可以顶一下或者评论一下吗? 思路: 这题复杂在要取前k大的结果,如果只是取最大情况下的金币和,直接 动态规划递归就可以,可是前k大并不能找出什么公式,所以 ...

  3. hdu 5008 查找字典序第k小的子串

    Boring String Problem Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Ot ...

  4. Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)(值域线段树第k大)

    Permutation UVA - 11525 看康托展开 题目给出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展开(将n个数的所有排列按字典序排序 ...

  5. 后缀自动机求字典序第k小的串——p3975

    又领悟到了一点新的东西,后缀自动机其实可以分为两个数据结构,一个是后缀树,还有一个是自动机 后缀树用来划分endpos集合,并且维护后缀之间的关系,此时每个结点代表的是一些后缀相同且长度连续的子串 自 ...

  6. 刷题-力扣-1738. 找出第 K 大的异或坐标值

    1738. 找出第 K 大的异或坐标值 题目链接 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/find-kth-largest-xor-co ...

  7. [LeetCode] Kth Largest Element in an Array 数组中第k大的数字

    Find the kth largest element in an unsorted array. Note that it is the kth largest element in the so ...

  8. POJ2985 The k-th Largest Group[树状数组求第k大值+并查集||treap+并查集]

    The k-th Largest Group Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 8807   Accepted ...

  9. 区间第K大(一)

    Problem: 给定无序序列S:[b, e),求S中第K大的元素. Solution 1.裸排序 2.现将区间均分成两段,S1, S2,对S1,S2分别排序,然后

随机推荐

  1. 【C#/WPF】图片的切割/切图/裁剪图片

    前台准备两个Image控件.上面是显示原图,下面显示切割后的效果. <StackPanel Orientation="Vertical"> <Image Widt ...

  2. u-boot 2016.05 添加u-boot cmd

    记录一下如何在u-boot 添加一个自己想要的命令. 首先来看一下宏,include/command.h 218 #define U_BOOT_CMD(_name, _maxargs, _rep, _ ...

  3. java中volatile关键字的含义<转>

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  4. 一、think in java 第一章

    一.面向对象程序设计方式: 1.万物皆为对象. 将对象视为奇特的变量,它可以存储数据,也可以要求它在自身上执行操作. 2.程序是对象的集合,它们通过发送消息来告知彼此所要做的. 要请求一个对象,就必须 ...

  5. kill 信号大全

    linux kill信号列表$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL5) SIGTRAP      6) S ...

  6. 二、Linux 静态IP,动态IP配置

    Linux 静态IP,动态IP配置 第一步:激活网卡 系统装好后默认的网卡是eth0,用下面的命令将这块网卡激活. # ifconfig eth0 up 第二步:设置网卡进入系统时启动 想要每次开机就 ...

  7. 软件测试工具MonkeyTalk使用方法

    1.简单介绍 MonkeyTalk软件测试工具由两部分构成:MonkeyTalk IDE 和 MonkeyTalk Agents MonkeyTalk IDE是Eclipse平台的工具,工能是:对iO ...

  8. C语言写的trim()函数

    C语言的标准库中缺少对字符串进行操作的trim()函数,使用起来有些不便,可以使用利用 strlen 和 isspace 函数以及指针来自己写一个. 1.strlen 函数 原型:extern int ...

  9. (转)深入解析SendMessage、PostMessage

        转自:http://blog.csdn.net/xt_xiaotian/article/details/2778689 本文将使用C++语言,在MFC框架的配合下给出PostMessage.S ...

  10. CentOS查看本机公网IP命令

    icanhazip.com 使你在任何地方知道你的公网IP地址 icanhazip.com是一个网址,你在浏览器中输入这个网址,你就能得到你的公网IP地址了. 我在Linux下一般使用curl ica ...