poj_1037 动态规划+字典序第k大

题目大意

给定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;
}
时间: 2024-10-24 09:31:47

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

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

Boring String Problem Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 1848    Accepted Submission(s): 492 Problem Description In this problem, you are given a string s and q queries. For each qu

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

Permutation UVA - 11525 看康托展开 题目给出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展开(将n个数的所有排列按字典序排序,并将所有排列编号(从0开始),给出排列的编号得到对应排列)用到的式子.可以想到用逆康托展开的方法.但是需要一些变化: for(i=n;i>=1;i--) { s[i-1]+=s[i]/(n-i+1); s[i]%=(n-i+1); } 例如:n=3时,3=0*2!+0*1!+3*0!应该变为3=1

51 nod 1105 第K大的数

1105 第K大的数 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题  收藏  关注 数组A和数组B,里面都有n个整数.数组C共有n^2个整数,分别是A[0] * B[0],A[0] * B[1] ......A[1] * B[0],A[1] * B[1]......A[n - 1] * B[n - 1](数组A同数组B的组合).求数组C中第K大的数. 例如:A:1 2 3,B:2 3 4.A与B组合成的C包括2 3 4 4 6 8 6 9 12共9个数. I

lintcode 中等题:kth-largest-element 第k大元素

题目 第k大元素 在数组中找到第k大的元素 样例 给出数组[9,3,2,4,8],第三大的元素是4 给出数组 [1,2,3,4,5],第一大的元素是5,第二大的元素是4,第三大的元素是3,以此类推 注意 你可以交换数组中的元素的位置 挑战 要求时间复杂度为O(n),空间复杂度为O(1) 解题 理论快速排序的思想,每次都减半,这个时间复杂度也是O(N),至于为什么就不知道了 class Solution { /* * @param k : description of k * @param num

从一个序列中获取前K大的数的一种方法

这个方法是利用快速排序的.在快速排序中,得到中间元素(pivot)之后,比较中间元素之前的元素个数和K的大小关系,从而确定后面该往哪个方向继续递归.如果中间元素前面的元素个数等于K,那就停止递归过程:如果中间元素前面元素个数小于K,那就再中间元素后面进行递归:否则就往中间元素前面进行递归.这样最终得到的是没有排序的前K大的元素,这样再对前K个元素进行一次真正的快速排序.这样就能得到排好序的前K大元素.我随机生成了一个100000个整型数据的文件进行测试,求前10000个元素.这样做用了0.984

统计前k大的数x

我终于敲上了题目--记起来啦! 描述 给定一个数组,统计前k大的数并且把这k个数从大到小输出. 输入 第一行包含一个整数n,表示数组的大小.n < 100000. 第二行包含n个整数,表示数组的元素,整数之间以一个空格分开.每个整数的绝对值不超过100000000. 第三行包含一个整数k.k < n. 输出 从大到小输出前k大的数,每个数一行. 样例输入 10 4 5 6 9 8 7 1 2 3 0 5 样例输出 9 8 7 6 5 //AC自动机x #include<iostream&

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

The k-th Largest Group Time Limit: 2000MS   Memory Limit: 131072K Total Submissions: 8807   Accepted: 2875 Description Newman likes playing with cats. He possesses lots of cats in his home. Because the number of cats is really huge, Newman wants to g

51nod 1686 第k大区间

1686 第K大区间 基准时间限制:1 秒 空间限制:131072 KB 分值: 40 难度:4级算法题 定义一个区间的值为其众数出现的次数.现给出n个数,求将所有区间的值排序后,第K大的值为多少. 众数(统计学/数学名词)_百度百科 Input 第一行两个数n和k(1<=n<=100000,k<=n*(n-1)/2) 第二行n个数,0<=每个数<2^31 Output 一个数表示答案. Input示例 4 2 1 2 3 2 Output示例 2 /* 51nod 1686

找出整数中第k大的数

一  问题描述: 找出m个整数中第k(0<k<m+1)大的整数. 二  举例: 假设有12个整数:data[1, 4, -1, -4, 9, 8, 0, 3, -8, 11, 2, -9],请找出第5大的数(容易知道是0). 三   算法思路:        一种基于快排思想的算法可以在O(n)复杂度内找到第k大的数,首先要知道partition这个函数,它可以调整一个序列 使小于key的元素都排在key左边,大于key的元素都排在key右边,key可以在这个序列中任意选择,一般选择给定序 列