每日刷题训练 2022.8.2 P1091 [NOIP2004 提高组] 合唱队形

彬彬不是0Ier

每日刷题训练 2022.8.2 P1091 [NOIP2004 提高组] 合唱队形

思路

原问题很容易转换到求:一个位置结束的最长上升子序列 和 结束的最长下降子序列的和,且要使和最大。答案就是总数减去这个最大和。

直接暴力枚举这个位置,再求长度即可。

但是 这样的暴力肯定能AC(时间复杂度 O ( n 3 ) O(n^3) O(n3) n ≤ 100 n\leq100 n100),所以我发挥了精益求精(闲的没事干)的浪费时间的优秀精神,进行了优化。

优化

O ( n 2 ) O(n^2) O(n2)优化

首先我们回到最长上升/下降子序列,状态的定义是不是:以当前位置结尾的最长上升/下降子序列的长度(包括自己)。

那最长上升子序列就可以直接用 O ( n 2 ) O(n^2) O(n2)的算法求,不需要枚举。

这里肯定有人说:这优化不和没有一样吗?时间复杂度不还是 O ( n 3 ) O(n^3) O(n3)(要枚举最长下降子序列的开始位置)。

那我们再来看看时间复杂的罪魁祸首——最长下降子序列。

我们换个思路想,倒着 做最长上升子序列。

那这时状态就变为了:从后往前 以当前位置结尾的 最长上升子序列的长度。

再变一下:

倒着是上升,那我正着不就是下降吗?

倒着是从后往前,那我正着不就是从前往后吗?

那我们的状态就变为:以当前位置开始的最长下降子序列的长度。但我们实现还是可以用 从后往前的 最长上升子序列。

O ( n 2 ) O(n^2) O(n2)拿下。

o ( n l o g n ) o(n {log_n}) o(nlogn)优化

O ( n 2 ) O(n^2) O(n2)的优化都码了,我相信你会有时间(闲的有空)打 o ( n l o g n ) o(n {log_n}) o(nlogn)的优化吧?

前置算法: o ( n l o g n ) o(n {log_n}) o(nlogn)的最长上升/下降子序列。

直接替换算法。

CODE

#include
using namespace std;

const int maxn=105;

int n,o,ans;
int a[maxn],f[maxn],g[maxn],tmp[maxn];

int lowb(int x) {return lower_bound(tmp+1,tmp+o+1,x)-tmp;}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);

    memset(tmp,0x5f,sizeof(tmp));
    tmp[0]=0;
    o=0;
    for(int i=1;i<=n;i++)
    {
        if(tmp[o]<a[i]) {o++,tmp[o]=a[i],f[i]=o;}
        else{tmp[lowb(a[i])]=min(a[i],tmp[lowb(a[i])]),f[i]=lowb(a[i]);}
    }

    memset(tmp,0x5f,sizeof(tmp));
    tmp[0]=0;
    o=0;
    for(int i=n;i>=1;i--)
    {
        if(tmp[o]<a[i]) {o++,tmp[o]=a[i],g[i]=o;}
        else{tmp[lowb(a[i])]=min(a[i],tmp[lowb(a[i])]),g[i]=lowb(a[i]);}
    }

    for(int i=1;i<=n;i++) ans=max(ans,f[i]+g[i]-1);
    printf("%d",n-ans);
}

此处查找使用lower_bound(查找大于等于自己的第一个数)。

你可能感兴趣的:(每日刷题,算法,动态规划,c++)