C/C++百题打卡[5/100]——合唱队形


⌛️



Chorus ☁️

上一题链接: C/C++百题打卡[4/100]——融合最大数 ⭐️⭐️ 考查数学.
百题打卡总目录: 🚧 🚧 …


一、题目总述

n n n 位同学站成一排,音乐老师要请其中的 k k k 位同学出列,使得剩下的 n − k n-k nk 位同学排成合唱队形。

合唱队形是指的是:设 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<...<tk1<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 2n100),表示同学的总数。

第二行有 n n n 个整数,用空格分隔,第 i i i 个整数 t i t_i ti 130 ≤ t i ≤ 230 130≤t_i≤230 130ti230)是第 i i i 位同学的身高(厘米)。

输出描述

一个整数,最少需要几位同学出列。

注意

①运行限制——>最大运行时间: 1 s 1s 1s,最大运行内存: 128 M 128M 128M; ②对于 50 50% 50 的数据,保证有 n ≤ 20 n≤20 n20,对于全部的数据,保证有 n ≤ 100 n≤100 n100

  ● 输入样例

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+len21。最后我们只要挑出里面最大的一个数就 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     

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一支王同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值