洛谷线性动态规划训练(2)动态规划组合状态:P1091 合唱队形——上升序列与下降结合

P1091 合唱队形 题解

洛谷线性动态规划训练(2)动态规划组合状态:P1091 合唱队形——上升序列与下降结合_第1张图片
洛谷线性动态规划训练(2)动态规划组合状态:P1091 合唱队形——上升序列与下降结合_第2张图片

输入输出样例
输入 #1复制
8
186 186 150 200 160 130 197 220
输出 #1复制
4

分析

这个题目还是很有意思的,因为它并不是直接给出了动态规划的方程,而是类似于最大子列和的方法。

在最大子列和中,我们的定义是dp[i]:以nums[i]为结尾的最大子列和,然后基于最后一步的连续性和dp[i-1]结合进行求解。

在最长升序序列中,我们将dp[i]定义为,以nums[i]为结尾的最长升序序列,然后让dp[i]和前面的dp[1]-dp[i-1]产生关系来进行求解。

在本题中,我们如果定义dp[i]为以num[i]为结尾的出列人数什么的好像没有什么用。换一种方法,似乎定义以num[i]为中心的留存人数/出列人数可能有些用,然后我们遍历之后求最大最小值就可以了。但是这好像有些难以直接计算,考虑到我们已经知道最大升序的求法了,我们可以使用这个方法来分别求升序列和降序列。

注意:这里我推荐用O(n^2)方法,因为它是定义很明确,必须以nums[i]结尾,如果用nlogn的方法,定义是没有这么明确的,即并不一定是以nums[i]结尾。

#include
#include
#include
#include
using namespace std;

int main() {
	int n;
	int nums[105];
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> nums[i];
	}
	int dp[105];//dp[i]的定义为:以nums[i]同学为中间,至少需要出列的人数

	//我们最终结果是求所有的dp[i]中的最小值,即出列人数的最小值
	//出列人数由左半部分和右半部分组成,左半部分是升序列,右半部分是减序列
	int left[105];
	int right[105];
	for (int i = 0; i < n; i++) {
		left[i] = 1;
		right[i] = 1;
	}
	//升序序列,以i为下标元素结尾
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < i; j++) {
			if (nums[i] > nums[j]) {
				left[i] = max(left[i], left[j] + 1);
			}
		}
	}
	//降序序列,以i为下标的元素为开头
	for (int i = n-1; i >= 0; i--) {
		for (int j = i; j nums[j]) {
				right[i] = max(right[i], right[j] + 1);
			}
		}
	}
	int res = INT_MAX;
	for (int i = 0; i < n; i++) {
		dp[i] = n - (right[i] + left[i] - 1);
		res = min(res, dp[i]);
	}
	cout << res;
	return 0;
}

你可能感兴趣的:(动态规划,洛谷训练)