C语言之合唱队形——动态规划

题目描述

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

合唱队形是指这样的一种队形:设 k 位同学从左到右依次编号为 1,2, ... ,k,他们的身高分别为 t1​,t2​, ... ,tk​,则他们的身高满足 t1​<⋯<ti​>ti+1​> ... >tk​(1≤i≤k)。

你的任务是,已知所有 n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入格式

共二行。

第一行是一个整数 n(2≤n≤100),表示同学的总数。

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

输出格式

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

cs 复制代码
输入
8
186 186 150 200 160 130 197 220
输出
4
说明/提示
对于 50% 的数据,保证有 n≤20。
对于全部的数据,保证有 n≤100。
cs 复制代码
#include <stdio.h>
#include <string.h>  // 为了 memset

int a[105];//用于存储输入的同学身高数
int b[105];//表示以i结尾的最长上升子序列长度是b[i]
int c[105];//表示以i结尾的最长下降子序列长度是c[i]

int main() {
    int n;
    scanf("%d", &n);
    
    // 初始化数组为0
    memset(b, 0, sizeof(b));
    memset(c, 0, sizeof(c));
    
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    
    // 计算最长上升子序列
    for (int i = 1; i <= n; i++) {
        b[i] = 1;  // 初始化为1(自己)
        for (int j = 1; j < i; j++) {
            if (a[i] > a[j] && b[j] + 1 > b[i]) {//如果定义的最高的人比前面的那个人更高,也就是满足判断条件,而且之前的人数加上这个人之后总人数比之前的大,那人数就进行替换。
            //这个替换刚好使不满足题意的人去掉,因为总人数取的还是之前的。
                b[i] = b[j] + 1;
            }
        }
    }
    
    // 计算最长下降子序列(从右向左的上升子序列)
    for (int i = n; i >= 1; i--) {
        c[i] = 1;  // 初始化为1
        for (int j = i + 1; j <= n; j++) {
            if (a[i] > a[j] && c[j] + 1 > c[i]) {
                c[i] = c[j] + 1;
            }
        }
    }
    
    // 找最大合唱队形长度
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (b[i] + c[i] > ans) {
            ans = b[i] + c[i];
        }
    }
    
    // 输出最少出列人数
    printf("%d\n", n - (ans - 1));
    
    return 0;
}

一.整体思路:

因为要求最少出列同学,所以间接说明在合唱队形中的人数越多越好。又要求中间必须是最高的,所以要从第一个人开始遍历,看看是否满足题意且上升序列的时候人数是最多的,下降序列的时候人数也是最多的。而既要遍历,又要人数最多,肯定是要进行最大值的替换。

动规五部曲:最重要的是递推公式

1.dp数组含义及下标含义

dp[i]刚好可以表示成以i为最高的人时,上升序列或下降序列的人数。根据分析,就要定义两个数组来表示。即上述b[i]和c[i]。

2.递推公式

利用已经计算好的子问题(b[j])来解决当前问题(b[i])。而刚计算好的b[i]又可以作为下一次的b[j]来计算。

if (a[i] > a[j] && b[j] + 1 > b[i]) {

b[i] = b[j] + 1;

}

怎么想到的递推公式呢?

我们要求的是:以i结尾的最长上升子序列长度

自然想法:

  • 一个以i结尾的上升子序列=某个以j结尾的上升子序列+a[i]。注意,前面的某个以j结尾的上升子序列,不一定是i-1=j,因为我们要取的是最大人数,所以肯定要选一个最合适的上升子序列,这个可能是任意一个。

  • 要求:a[j] < a[i](这样才能接上)

  • 长度 = 那个子序列的长度 + 1

复制代码
b[i] = max( b[j] + 1 )  对于所有 j < i 且 a[j] < a[i]

为什么这样想?

  • 我们不知道哪个 j 最好,所以要枚举所有可能的 j

  • 对每个可能的 j,计算 b[j] + 1

  • 取最大值就是答案

  1. 先假设最坏情况:b[i] = 1(只有自己)

  2. 然后尝试改进:对每个 j,看看能不能让 b[i] 变大

    • 如果能接在 j 后面(a[i] > a[j])

    • 而且接上后比当前记录的长度更长(b[j] + 1 > b[i])

    • 就更新记录

3.dp数组如何进行初始化

那就只能根据实际意义来写了。因为我们要算的是人数是否是最大的,当i表示的那个人不符合题意时,肯定这个队列中只有这一个人,因为其他人都不符合题意都被去掉了,所以可以将每一次便利的时候都初始化为1,反正是最小的最大值,不会影响后面的判断。

4.遍历顺序

很好理解。我就不分析了。

5。打印dp数组

三.解析代码:主要是上升序列代码和下降序列代码

if (a[i] > a[j] && b[j] + 1 > b[i]) {

b[i] = b[j] + 1;

}

因为我们之前计算的b[j]都是符合题意的,都满足if语句条件,就是在计算当i=1,2,3时计算出来的真实的值,所以不需要再进行判断,所以在后面就可以直接利用。但是要不要计算加1取决于前面的条件

b[j] + 1 > b[i]

  • 含义 :如果把 i 接在 j 后面,得到的长度比当前记录的长度更长
  • b[j]:以 j 结尾的最长上升子序列长度

  • b[j] + 1:把 i 接在 j 后面得到的新长度

  • b[i]:当前记录的以 i 结尾的最长长度

举个例子:

假设身高数组:

复制代码
186 186 150 200 160 130 197 220
  • i=1 (186):左边没人 → b[1] = 1

  • i=2 (186):左边 186,a[1]=186 不小于 186 → b[2] = 1

  • i=3 (150):左边都比 150 大 → b[3] = 1

  • i=4 (200):

    • j=1: 186<200 → b[1]+1=2因为满足条件判断,所以要将之前的总数加上这个符合题意的人数。

    • j=2: 186<200 → b[2]+1=2这些都是根据上述计算出来的b[],再来计算的

    • j=3: 150<200 → b[3]+1=2

    • 最大=2 → b[4]=2

  • i=5 (160):

    • j=1: 186>160 ❌

    • j=2: 186>160 ❌

    • j=3: 150<160 → b[3]+1=2

    • j=4: 200>160 ❌

    • 最大=2 → b[5]=2

  • i=6 (130):

    • 所有左边都大于130 → b[6]=1

............

相关推荐
weisian1513 小时前
JVM--11-什么是 OOM?深度解析Java内存溢出核心概念与原理(上)
java·开发语言·jvm·oom
fengfuyao9853 小时前
基于对数似然比(LLR)的LDPC译码器的MATLAB实现
开发语言·matlab
Java后端的Ai之路3 小时前
【AI应用开发工程师】-分享Java 转 AI成功经验
java·开发语言·人工智能·ai·ai agent
IT猿手3 小时前
基于分解的多目标进化算法(MOEA/D)求解46个多目标函数及一个工程应用,包含四种评价指标,MATLAB代码
开发语言·算法·matlab
落羽的落羽3 小时前
【C++】深入浅出“图”——最短路径算法
java·服务器·开发语言·c++·人工智能·算法·机器学习
叙白冲冲3 小时前
Java中Arrays静态方法
java·开发语言
手握风云-3 小时前
JavaEE 进阶第十八期:MyBatis,查询请求的生命周期全景图(三)
java·开发语言·java-ee
每天要多喝水3 小时前
动态规划Day29:打家劫舍
算法·动态规划
.小小陈.3 小时前
Python基础语法详解4:函数、列表与元组全解析
开发语言·c++·python·学习