⌛️
Chorus ☁️
上一题链接: C/C++百题打卡[4/100]——融合最大数 ⭐️⭐️ 考查数学.
百题打卡总目录: 🚧 🚧 …
一、题目总述
n n n 位同学站成一排,音乐老师要请其中的 k k k 位同学出列,使得剩下的 n − k n-k n−k 位同学排成合唱队形。
合唱队形是指的是:设 n n n 位同学从左到右依次编号为 1 , 2 , … k , . . . , n 1,2, … k,...,n 1,2,…k,...,n。
且他们的身高分别为 t 1 < t 2 < . . . < t k − 1 < t k > t k + 1 > . . . > t n t_1<t_2<...<t_{k-1}<t_k>t_{k+1}>...>t_n t1<t2<...<tk−1<tk>tk+1>...>tn
注:“ t 1 < t 2 < . . . t n t_1<t_2<...t_n t1<t2<...tn” 和 “ t 1 > t 2 > . . . > t n t_1>t_2>...>t_n t1>t2>...>tn” 也算合唱队形
你的任务是,已知所有 n n n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
● 输入描述:
共二行。
第一行是一个整数 n n n( 2 ≤ n ≤ 100 2≤n≤100 2≤n≤100),表示同学的总数。
第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti ( 130 ≤ t i ≤ 230 130≤t_i≤230 130≤ti≤230)是第 i i i 位同学的身高(厘米)。
● 输出描述:
一个整数,最少需要几位同学出列。
● 注意:
①运行限制——>最大运行时间: 1 s 1s 1s,最大运行内存: 128 M 128M 128M; ②对于 50 50% 50 的数据,保证有 n ≤ 20 n≤20 n≤20,对于全部的数据,保证有 n ≤ 100 n≤100 n≤100。
● 输入样例:
8
190 186 150 200 160 130 197 120
● 输出样例:
3
● 样例解释:一种最佳出列方式是——>第一二位同学和倒数第二位同学出列。剩下5个组成合唱队形。
二、思考空白区
● 题目难度:⭐️⭐️⭐️⭐️
● 建议思考时间:⌛️ ⌛️
三、题目解析
● 这是一道考查数组
的题。
● 相信大家拿到这道小题后,最直观的感受是 一一> 找一个 “小山丘”,以身高为对比量的 “小山丘”,而且越 “长” 越好。如下图所示
● 那如何去找设计一个算法来实现该功能呢?也就是如何找到这个最佳的 “小山丘” ?
● 一来我想到的最简单的思路是:从左到右,依次遍历每一个同学,当遍历到第 k k k 个同学时,Ta 就向左望去,看左边的 “最长增序列” 的长度 l e n 1 len_1 len1(包含该同学本身),然后再向右望去,看他右边的 “最长减序列” 的长度 l e n 2 len_2 len2(包含该同学本身),那么对于 Ta 来说,如果以它为合唱队的 “最高峰”,那么该合唱队的人数为 l e n 1 + l e n 2 − 1 len_1 + len_2 - 1 len1+len2−1。最后我们只要挑出里面最大的一个数就 OK 了。
● 但问题来了,怎么求 “最长增序列的长度” 呢?(如果会求它了,那求 “最长减序列” 也不成问题了)
也就是说,比如,我们要求序列 “4 1 2 1 3 5 1 7” 的最长增序列的长度(注意是严格递增),很显然,“1 2 3 5 7” 的长度便是,即最长增序列的长度为 5 ,如下图所示:
● 这个用眼睛看,还是能一眼看出来的,但如果数组十分的长,那就不好看出来了。那怎么计算呢?这里提供一种简单实用的方法是:
用一个数组
height[]
来记录 “以该位同学为终点,从左到右的的最长增序列” 的长度。
当要获取height[k]
时,就找出下标为 j ( 且 j < k ) j(且j<k) j(且j<k),且height[j] < height[k]
,且height[j]
为下标j<k
中的身高最大的一个值
● 示意图如下:
● 诶,既然会求 “最长增序列的长度”,那么 “最长减序列的长度” 就迎刃而解了(即反向沿着
序列求个最长增序列长度即可)。
● 想到这里的时候,我突然想到,一个一个同学的遍历,然后遍历到每一个同学都要重新计算一遍以 ta 为“最高峰”的两边的height[]
数组,虽然可行,但挺麻烦的,好像可以简化。
● 经过一番思考,当我的左手比出一个上坡,右手比出一个下坡时,诶,突然想到一个好方法,那就是接下来的算法思路:
① 先以第 i i i 位同学为终点,求出从左到右(该位同学为终点)的最长子序列的长度,分别存储在 h e i g h t 1 [ i ] height_1[i] height1[i] 中
② 再以第 j j j 位同学为终点,求出从右到左(该位同学为终点)的最长子序列的长度,分别存储在 h e i g h t 2 [ j ] height_2[j] height2[j] 中
③ 在遍历每一位同学(假设第 k k k 位),只需找出所有 h e i g h t 1 [ k ] + h e i g h t 2 [ k ] − 1 height_1[k] + height_2[k] - 1 height1[k]+height2[k]−1 中最大的数,即是当前合唱队伍的最大人数
④ 题目要求算出出去的最少人数,所以答案 = 所有人数 - 当前合唱队伍的最大人数
● 示意图如下:
● 说明:可以发现,其实最终的答案有两个(若按照题目的要求),只要任意考虑一种即可。
● 算法设计:
[1]
设计好 输入输出模块。
[2]
设计好 最长子序列初始化模块 :
[3]
设计好 求最大数模块
第[1]步:输入输出模块
● 这是比较容易的事:
#include<stdio.h>
int main()
{
/* 输入模块 */
int n;
int a[110]; // 按照题目要求,空间只要大于 100 即可
int rev_a[110]; // rev 在代码界是 “reverse” 的缩写, 我们定义该数组就是为了一会儿方便求 height_2[]
scanf("%d", &n);
for (i = 0; i < n; i++)
{
scanf("%d", &a[i]);
rev_a[ n-i-1 ] = a[i]; // rev_a[] 是 a[] 的逆序。
}
/* 最长子序列初始化模块 */
...
/* 求最大数模块 */
...
/* 输出模块 */
pritnf("%d", 答案);
return 0;
}
第[2]步:最长子序列初始化模块
#include<stdio.h>
/*
* 函数功能:找出 包含第 i 位同学和前面同学序列 的最长增子序列长度。
* 参数说明:
* [1] arr[]: 身高数组
* [2] height[]: 即是 height_1[] 和 height_2[] 的“替身”,存储子序列的数组
* [3] N: 即是遍历到第 i 位同学的下标 i 。
*/
int find(int arr[], int height[], int N)
{
int best_num = -1;
int cur_height = arr[N];
for (int i = 0; i < N; i++)
{
if (arr[i] < cur_height) // 首先,前面的某一同学的身高比第 i 位同学小才行
{
if (height[i] > best_num) // 其次,若能更新最大子序列值,则更新。
{
best_num = height[i];
}
}
}
if (best_num == -1) // 若该值未变,则说明:第 i 位同学 目前 是最矮的之一
return 1;
else
return best_num + 1; // 否则返回 +1 值
}
int main()
{
/* 输入模块 */
...
/* 最长子序列初始化模块 */
int height_1[110], height_2[110];
for (int i = 0; i < n; i++) // 先初始化
height_1[i] = height_2[i] = 1;
for (i = 0; i < n; i++) // 再做处理
{
height_1[i] = find(a, height_1, i);
height_2[i] = find(rev_a, height_2, i);
}
/* 求最大数模块 */
...
/* 输出模块 */
...
return 0;
}
第[3]步:求最大数模块
● 这个相对简单点,也就是在一组数中找出最大值,只不过多了一点计算。
/* 求最大数模块 */
int max_num;
for (i = 0; i < n; i++)
{
int j = n - i - 1; // 注意: 我们的 height_2[] 是基于 rev_a[] 求出的,
// 而 rev_a[] 是 a[] 的倒序, 所以最后我们也要倒过来
if (max_num < height_1[i] + height_2[j] - 1)
{
max_num = height_1[i] + height_2[j] - 1;
}
}
/* 输出模块 */
pritnf("%d", n - max_num); // 记得最后要减一下
return 0;
四、做题小结与反思
● 解决问题往往是先考虑如何先解决,然后考虑如何去优化。这道题便是。
● 结合了 倒序 + 辅助数组(height_1[]
和height_2[]
) 的方法来简化了问题。
五、完整代码(C和C++版)
● C 语言版本:
#include<stdio.h>
/*
* 函数功能:找出 包含第 i 位同学和前面同学序列 的最长增子序列长度。
* 参数说明:
* [1] arr[]: 身高数组
* [2] height[]: 即是 height_1[] 和 height_2[] 的“替身”,存储子序列的数组
* [3] N: 即是遍历到第 i 位同学的下标 i 。
*/
int find(int arr[], int height[], int N)
{
int best_num = -1;
int cur_height = arr[N];
for (int i = 0; i < N; i++)
{
if (arr[i] < cur_height) // 首先,前面的某一同学的身高比第 i 位同学小才行
{
if (height[i] > best_num) // 其次,若能更新最大子序列值,则更新。
{
best_num = height[i];
}
}
}
if (best_num == -1) // 若该值未变,则说明:第 i 位同学 目前 是最矮的之一
return 1;
else
return best_num + 1; // 否则返回 +1 值
}
int main()
{
/* 输入模块 */
int a[110], rev_a[110];
int i, n;
scanf("%d", &n);
for (i = 0; i < n; i++)
{
scanf("%d", &a[i]);
rev_a[n - i - 1] = a[i];
}
/* 最长子序列初始化模块*/
int height_1[110], height_2[110];
for (int i = 0; i < n; i++)
height_1[i] = height_2[i] = 1;
for (i = 0; i < n; i++)
{
height_1[i] = find(a, height_1, i);
height_2[i] = find(rev_a, height_2, i);
}
/* 求最大数模块 */
int max_num = 0;
for (i = 0; i < n; i++)
{
int j = n - i - 1;
if (max_num < height_1[i] + height_2[j] - 1)
{
max_num = height_1[i] + height_2[j] - 1;
}
}
/* 输出模块 */
printf("%d", n - max_num);
return 0;
}
● 运行结果:
● C++ 版本:
#include <iostream>
using namespace std;
/*
* 函数功能:找出 包含第 i 位同学和前面同学序列 的最长增子序列长度。
* 参数说明:
* [1] arr[]: 身高数组
* [2] height[]: 即是 height_1[] 和 height_2[] 的“替身”,存储子序列的数组
* [3] N: 即是遍历到第 i 位同学的下标 i 。
*/
int find(int arr[], int height[], int N)
{
int best_num = -1;
int cur_height = arr[N];
for (int i = 0; i < N; i++)
{
if (arr[i] < cur_height) // 首先,前面的某一同学的身高比第 i 位同学小才行
{
if (height[i] > best_num) // 其次,若能更新最大子序列值,则更新。
{
best_num = height[i];
}
}
}
if (best_num == -1) // 若该值未变,则说明:第 i 位同学 目前 是最矮的之一
return 1;
else
return best_num + 1; // 否则返回 +1 值
}
int main()
{
/* 输入模块 */
int a[110], rev_a[110];
int i, n;
cin >> n;
for (i = 0; i < n; i++)
cin >> a[i], res_a[n - i - 1] = a[i];
/* 最长子序列初始化模块*/
int height_1[110], height_2[110];
for (int i = 0; i < n; i++)
height_1[i] = height_2[i] = 1;
for (i = 0; i < n; i++)
{
height_1[i] = find(a, height_1, i);
height_2[i] = find(rev_a, height_2, i);
}
/* 求最大数模块 */
int max_num = 0;
for (i = 0; i < n; i++)
{
int j = n - i - 1;
if (max_num < height_1[i] + height_2[j] - 1)
{
max_num = height_1[i] + height_2[j] - 1;
}
}
/* 输出模块 */
cout << n - max_num;
return 0;
}
六、参考附录
[1] 原题地址:https://www.luogu.com.cn/problem/P1091.
上一题链接: C/C++百题打卡[4/100]——融合最大数 ⭐️⭐️ 考查数学.
百题打卡总目录: 🚧 🚧 …
C/C++百题打卡[5/100]——合唱队形 [题目源自 洛谷 ] ⭐️ ⭐️ ⭐️ ⭐️
标签:数组、动态规划、单调队列、NOIp 提高组 2004
一周一更
2021/12/19