洛谷P1091合唱队形(最长上升子序列)

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

合唱队形是指这样的一种队形:设 $k$ 位同学从左到右依次编号为 1,2, … ,k,他们的身高分别为 t_1,t_2, … ,t_k,则他们的身高满足 t_1<t_i>t_{i+1}> … >t_k。

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

## 输入格式

共二行。

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

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

## 输出格式

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

 样例输入 #1
8
186 186 150 200 160 130 197 220

## 提示

对于 50% 的数据,保证有n <= 20。

对于全部的数据,保证有 n <= 100。

在讲这道题之前,先来讲一下最长上升子序列:

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

假设我们有7个数,分别为3 1 2 1 8 5 6,那么他的最长上升子序列应该为1 2 5 6

所以答案为4。

现在来讲一讲这道题目用代码应当如何实现。

首先,这是一道动态规划的题目,我们用集合的方式进行分析

 我们是要求最长上升子序列,所以集合的属性应该是MAX,这里的状态计算我来解释一下。

我们已经确定,该最长上升子序列中的最后一位数已经确定,那么我们所找到的该序列中的倒数第二个数应该有 i 种可能。这里 0 代表这个上升子序列中没有倒数第二个数,(即 f[i] 为上升子序列中的唯一的一个数)。其他的1 , 2 , 3 表示 f[i] 的前一个数为提供的数组中的第 i 个数。

假设这 f[i] 的前一个数是 f[j] , 因为是上升子序列 , 所以 f[j] < f[i]

因此,当满足上述条件时,f[i] = max(f[i] , f[j] + 1)。

最后我们再次遍历一遍 f[i] 找到以 f[i]结尾最长的上升子序列。

这里我们先来coding

动态规划主要难的点在于理解思路,代码量和难度其实是比较小的

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010;
int n;
int num[N] , dp[N];
int main()
{
    cin >> n;
    for (int i = 0; i < n; i ++ ) cin >> num[i];
    for (int i = 0; i < n; i ++ ){
        dp[i] = 1;
        for(int j = 0 ; j < i ; j ++){
            if(num[i] > num[j]){
                dp[i] = max(dp[i] , dp[j] + 1);
            }
        }
    }
    int res = 0;
    for (int i = 0; i < n; i ++ ) res = max(res , dp[i]);
    cout << res;
    return 0;
}

大家可以拿之前的测试用例 或者是 自己想一个测试用例进行测试

现在,来回归这道题目吧,上述题目如果理解了,这道题就只是需要一点点小技巧。这道题目是求得去除i个人后能保证

 这样的一个最长的序列。我们可以把他看做两个最长上升子序列,这里的一个是从左到右的,另一个是从右到左的。问题的关键就变成了如何求这个中心点。

这里如果我们把所有点都遍历一遍,那么时间复杂度就是n^3 , 这样的时间复杂度有点太高了(当然,想这样做也不是不可以)。

我们可以使用一下前缀和,用数组表示每一个点的从左向右的最长上升子序列和从右向左的最长上升子序列。将两个相加然后 - 1(去掉一个重复的我们选择的顶点)

也就是假设f[i]是从左向右的最长上升子序列 , g[i]是从右向左的最长上升子序列,那么整个最长的合唱队长度就是f[i] + g[i] - 1

我们要求得需要去除多少人 , 所以我们可以用总人数减去该最长序列的人数即可

代码如下

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 110;
int n;
int g[N] , f[N] , a[N];
int main()
{
    cin >> n;
    int res = 0;
    for(int i = 1 ; i <= n ; i ++) cin >>a[i];
//从左向右的最长上升子序列
    for(int i = 1 ; i <= n ; i ++){
        g[i] = 1;
        for(int j = 1 ; j < i ; j ++){
            if(a[i] > a[j]) g[i] = max(g[i] , g[j] + 1);
        }
    }
//从右向左的最长上升子序列
    for(int i = n ; i >= 1 ; i --){
        f[i] = 1;
        for(int j = n ; j > i ; j --){
            if(a[i] > a[j]) f[i] = max(f[i] , f[j] + 1);
        }
    }
    for(int i = 1 ; i <= n ; i ++){
        res = max(res , g[i] + f[i] - 1);
    }
    cout << n - res;
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值