洛谷P1091 [NOIP2004 提高组] 合唱队形

题面:

解题:

数据范围n≤100非常善良~这意味着我们足以运行时间复杂度O(n²)甚至O(n³)的算法。

由于题目要求身高不能相等,我们不妨将相邻的同身高的两个同学先释放掉一个……

    cin >> t[1];
    for (int i = 2; i <= n; i++)
    {
        int temp;  cin >> temp;
        if (temp == t[i - 1]) { continue; }  //除去相邻同身高的人
        t[++tLen] = temp;
    }

算法分析:

据题意,一定有一位最高的同学Ti,其前、后同学身高依次递减……

由于数据范围较小,我们不妨试着求出每个同学作为Ti时需要除去的人数,再取最小值。

因此,问题便可以转化为:求从Ti出发的左侧最长下降子链右侧最长下降子链长度之和

DP求最长下降子链长度

例如:对于4 、1 、2 、4 、5 、1这样一组数据,我们用down[ i ]储存第1~第i个数的子链相关数据,len=1为最长下降子链长,则down[1]=t[1]=4。遍历后面的第2~6号数,以下模拟dp过程:

  • 对于t[2]=1:比down[len]=down[1]=4小,追加,即down[++len]=t[2]=1 ; [4,1]

  • 对于t[3]=2:比down[len]=down[2]=1大,不追加;

但比down[2]=1大,比down[1]=4小,更具潜力,将1替换成2:down[2]=2,[4,2]

  • 对于t[4]=4:比down[len]=2大,不追加; 和4等大,不替换; [4,2]

  • 对于t[5]=5:比起始元素4都大,直接跳过; [4,2]

  • 对于t[6]=1:比down[len]=2小,down[++len]=t[6]=1; [4,2,1]

至此,我们得到了最长递减子链长度:len=3

若无法说服自己可以动笔算算,详见getRDownLen代码:

int getRDownLen(int left, int right)   //获取从t[left]右侧的最长下降子链
{
    int down[105];
    int len = 1;
    down[1] = t[left];
    for (int i = left + 1; i <= right; i++)
    {
        if (t[i] < down[len])   //比队尾元素更小,直接加入队列末端
        {
            down[++len] = t[i];
            continue;
        }
        for (int j = len; j >= 2; j--)   //从len开始向前枚举
            if (t[i] > down[j] && t[i] < down[j - 1])  //比down[j]大且比down[j-1]小,则覆盖down[j]
               down[j] = t[i];
    }
    return len - 1;  //输出不算自身
}

AC代码奉上

#include<iostream>
#include<algorithm>

using namespace std;
const int MAXN = 1e6 + 5;

int n, t[105], ans = MAXN, tLen = 1;

int getLDownLen(int left, int right)   //获取从t[left]左侧的最长下降子链
{
    int down[105];
    int len = 1;
    down[1] = t[right];
    for (int i = right - 1; i >= left; i--)
    {
        if (t[i] < down[len])   //比队尾元素更小,直接加入队列末端
        {
            down[++len] = t[i];
            continue;
        }
        for (int j = len; j >= 2; j--)   //从len开始向前枚举
            if (t[i] > down[j] && t[i] < down[j - 1])   //比down[j]大且比down[j-1]小,则覆盖down[j]
                down[j] = t[i];
    }
    return len - 1;  //输出不算自身
}

int getRDownLen(int left, int right)   //获取从t[left]右侧的最长下降子链
{
    int down[105];
    int len = 1;
    down[1] = t[left];
    for (int i = left + 1; i <= right; i++)
    {
        if (t[i] < down[len])   //比队尾元素更小,直接加入队列末端
        {
            down[++len] = t[i];
            continue;
        }
        for (int j = len; j >= 2; j--)   //从len开始向前枚举
            if (t[i] > down[j] && t[i] < down[j - 1])  //比down[j]大且比down[j-1]小,则覆盖down[j]
               down[j] = t[i];
    }
    return len - 1;  //输出不算自身
}

int main()
{
    cin >> n;
    if (n == 2) { cout << "0" << endl; return 0; }
    cin >> t[1];
    for (int i = 2; i <= n; i++)
    {
        int temp;  cin >> temp;
        if (temp == t[i - 1]) { continue; }  //除去相邻同身高的人
        t[++tLen] = temp;
    }

    for (int i = 1; i <= tLen; i++)
    {
        int len1 = getLDownLen(1, i);
        int len2 = getRDownLen(i, tLen);
        int addLen = len1 + len2 + 1;   //总剩余队列长度
        ans = min(ans, n - addLen);
    }

    cout << ans << endl;
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值