题意:有一串数字,那么最多去掉其中的某一个数字,最多能得到一个多长的严格单调上升序列。
思路:把这个数组从前往后一次遍历,如果遇到一个将要破坏这个单调序列的数 那么有两种处理办法:
1、去掉这个数 看能否构成单调序列
2、去掉这个数前面的那个数 看能否构成单调序列
但是直接做不好处理:
- 不知道在哪里使用了这个删掉一个元素的机会 ;
- 不知道删掉了哪一个元素(是本身的这个元素还是之前的元素,这样对后面的状态会有影响);
- 而且如果直接递推的话,不知道使用了几次删去元素的机会。
那就创建一个dp数组 记录在某一个位置不使用删去的机会和使用这个机会所能形成的最长序列长度
dp[i][j]
//i 表示这个数的位置(第i个数)
//j=0 表示不使用这个机会
//j=1 表示使用这个机会
如果不使用这个机会的话
if 这个数 > 前一个数 则 dp[i][0]=dp[i-1][0]+1
if 这个数 <= 前一个数 则 dp[i][0]=1 (以这个破坏了序列的数为起点再开始计数)
如果使用这个机会的话
那就有两种情况 :
- 一种是 这个机会在这个位置用掉(删掉这个位置之前的那个数)——这种情况要保证这个数比前前个数要大
- 一种是 这个机会在这个位置之前用掉了 ——这种情况要保证这个数比前一个数要大
但是还要注意一种情况:这个数比前一个数要小,比前前个数也要小 那么就都只能从头开始算了
编码:主要是在dp上,怎么写dp的状态转移?
是以 是否使用这个删数的机会为条件来写
还是以 这个位置以前面的数的比较为条件来写呢?
我觉得 使用后者能使得编码更加简单,因为在前者的条件中需要重复判断数与数之间的大小比较关系
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int dp[200010][2];
int num,numl,numll;
int maxx,n;
int main(){
while(~scanf("%d",&n)&&n){
memset(dp,0,sizeof(dp));
numl=0;
numll=0;
maxx=1;
for(int i=1;i<=n;i++){
scanf("%d",&num);
//dp[i][0] 代表在位置i未删去元素
//dp[i][1] 代表在位置i删去了元素(可能在之前的某个位置删了一次)
if(num>numl){
dp[i][0]=dp[i-1][0]+1;
dp[i][1]=dp[i-1][1]+1;
}
else{
maxx=max(maxx,max(dp[i][0],dp[i][1]));
dp[i][0]=1;
dp[i][1]=1; //这个在后面num>numll的情况下还会再次判断
}
if(num>numll){
dp[i][1]=max(dp[i][1],dp[i-2][0]+1);
}
maxx=max(maxx,max(dp[i][0],dp[i][1]));
numll=numl;
numl=num;
}
printf("%d\n",maxx);
}
return 0;
}