彬彬不是0Ier
每日刷题训练 2022.8.2 P1091 [NOIP2004 提高组] 合唱队形
思路
原问题很容易转换到求:一个位置结束的最长上升子序列 和 结束的最长下降子序列的和,且要使和最大。答案就是总数减去这个最大和。
直接暴力枚举这个位置,再求长度即可。
但是 这样的暴力肯定能AC(时间复杂度 O ( n 3 ) O(n^3) O(n3)且 n ≤ 100 n\leq100 n≤100),所以我发挥了精益求精(闲的没事干)的浪费时间的优秀精神,进行了优化。
优化
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<bits/stdc++.h>
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(查找大于等于自己的第一个数)。