问题描述
给定有 n 个数的 A 序列:A1,A2,A3…An 。对于这个序列,我们想得到一个子序列 Ap1,Ap2⋯Api⋯Apm(1≤p1< p2<⋯pi<⋯< pm≤n),满足 Ap1≥Ap2≥⋯≥Api≤⋯≤Apm 。从 A 序列最少删除多少元素,可以得到我们想要的子序列。
输入格式
第一行输入一个整数 n,代表 A 序列中数字的个数。第二个输入 n 个整数,代表A1,A2 ,A3 …An。(1≤n≤1000,1≤Ai≤10000)
输出格式
输出需要删除的元素个数,占一行。
样例输入
7
3 2 4 1 2 5 3
样例输出
2
分析:
仔细阅读题目我们可以发现,这道题描述的符合序列的特点是“v”形,也就是先减后增。也就是中间会有一个拐点。所以我们只要找到最长的“v"形序列,然后拿序列的总长度减去该符合条件的序列的长度即可。
那么,我们就要集中分析如何找到该"v"形的最长长度。如果运用dp思想的解决,我们让要找到前一状态与目前状态之间的联系。
一开始的思路是,开一个两维的dp数组,标记为两个状态,一个是递减的(非严格的递减),一个是递增(非严格的递增)。然后开始遍历,根据这个元素a[i]与a[0]~a[i-1]进行比较,如果大于前一个元素,则dp[i][0]=dp[j][0]+1;否则dp[i][1]=dp[j][1]+1;
但是继续按照这个思路进行的话,相当于把每个dp[i][0],dp[i][1]当成一个"v"形的序列。
也就是把”v"序列的最后一个元素的dp当成一个状态。其实这个思路进行到这个地方有点问题了。导致后边的思路是十分的复杂。
正解的思路是,是把每个遍历的i当成一个拐点,也就是把每一个“v"形序列分成两半。也即是,将这个问题拆成了两个问题,第一个问题就是求解最长的下降序列,第二问题求解最长的上升序列。分别用dp1,dp2保存;
所以ans=max(dp1[i]+dp2[i]-1,ans);
最终是n-ans;
以下是代码:
#include<iostream>
using namespace std;
int main(){
int n;
cin >> n;
int dp1[1009];
int dp2[1009];
int a[1009];
int ans=0;
for(int i=0;i<n;i++){
cin >> a[i];
}
for(int i=0;i<n;i++){
dp1[i]=1;
dp2[i]=1;
}
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(a[i]<=a[j]){
dp1[i]=max(dp1[i],dp1[j]+1);//最长下降序列
}
}
}
for(int i=n-2;i>=0;i--){
for(int j=n-1;j>i;j--)
if(a[i]<=a[j]){
dp2[i]=max(dp2[i],dp2[j]+1);//最长上升序列
}
}
for(int i=0;i<n;i++){
ans=max(ans,dp1[i]+dp2[i]-1);
}
cout << n-ans;
return 0;
}