题目
合唱队形
此题可以用动态规划,也可以用贪心和二分
思路
此题就是求一个数组中山脉数组的最大长度。思路很直观,这里直接给出算法:
- 记数组元素个数为 n n n,遍历数组每个元素,对每个元素:
(1) 求出这个元素左边从左到右的最长上升子序列的长度(包含这个元素) l 1 l_1 l1
(2) 求出这个元素右边从右到左的最长上升子序列的长度(包含这个元素) l 2 l_2 l2
(3) 该元素的最长山脉数组长度为 k i = l 1 + l 2 − 1 k_i=l_1+l_2-1 ki=l1+l2−1
(4) 求出所有 k i k_i ki 的最大值 k m a x k_{max} kmax,即为剩下的合唱队形的人数- 输出答案 n − k m a x n-k_{max} n−kmax 即可。
有了思路并不够,还有许多细节需要考虑,比如怎么求解 l 1 l_1 l1 和 l 2 l_2 l2 等。也就是将原问题转为了求最长上升子序列的问题。这里有两种方法:一是动态规划,二是贪心+二分,在这里都可以用,不过动态规划方法的时间复杂度不如后一种方法,所以这里只记录二分法。具体见代码。
代码
#include <limits.h>
#include <stdio.h>
#define N 105// 二分法找出数组中第一个大于等于目标值的下标,没有则返回数组长度
int first_GE(int* arr, int n, int target) {int l = 0, r = n - 1, mid = 0;while (l <= r) {mid = ((r - l) >> 1) + l;if (arr[mid] >= target) {if (!mid || arr[mid - 1] < target) {return mid;} else {r = mid - 1;}} else {l = mid + 1;}}return n;
}// 求一段数组的最长上升子序列长度
int get_len(int* arr, int start, int end, int* aux, int n, int flag) {if (start == end) return 1;int i = 0, j = 0, len = 0;for (i = 0; i < n; i++) {aux[i] = INT_MAX;}if (flag) {// 从左往右求最大上升子序列长度for (i = start; i <= end; i++) {j = first_GE(aux, n, arr[i]);if (j > len) len = j;aux[j] = arr[i];}} else {// 从右往左求最大上升子序列长度for (i = end; i >= start; i--) {j = first_GE(aux, n, arr[i]);if (j > len) len = j;aux[j] = arr[i];}}return len + 1;
}int main(void) {int n = 0, a[N], b[N], i = 0;scanf("%d", &n);for (i = 0; i < n; i++) {scanf("%d", a + i);}int ans = 0, t = 0;for (i = 0; i < n; i++) {t = get_len(a, 0, i, b, N, 1) + get_len(a, i, n - 1, b, N, 0) - 1;if (t > ans) ans = t;}printf("%d\n", n - ans);return 0;
}