复杂点的dp

最长平稳上升子序列

https://codeforces.com/contest/1367/problem/F2

对于一个序列定义这样一种操作:取出任意一个数,放在数列最前面或者最后面。求最少操作多少次可以让序列变得有序。数字有可能重复,2e5。

简单思考后我们发现,其实就是需要我们找到最长的、平稳上升(相邻数字增加1或相同)的子序列,这样的序列可以不用动,只需要把应该位于它两侧的数字取出并放在它两侧即可,所以最少操作次数=n-maxlen。

最长平稳上升子序列(以下简称子序列)的构成遵循这样的特征:位于中间段的数字必须包括数列中全部的该数字,而位于两侧的数字则可以只是一部分,例如24324,234满足要求。这样一来dp就分为两部分,我们先求出整块的数字能构成的最长子序列,然后对于两侧加上所有能加的首尾元素,这个直接对元素的位置用lower_bound即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<bits/stdc++.h>
using namespace std;

const int maxn=2e5+4;

int t,n,a[maxn],_a[maxn];
int dp[maxn],l[maxn],r[maxn],start[maxn]; //整块的大小、某值左界、某值右界、某值整块的左界
vector<int> pos[maxn];

int main(){
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
_a[i]=a[i];
}
sort(_a+1,_a+1+n);
int tot=unique(_a+1,_a+1+n)-_a-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(_a+1,_a+1+tot,a[i])-_a;
}
memset(dp+1,0,sizeof(int)*n);
for(int i=1;i<=tot;i++)
pos[i].clear();
for(int i=1;i<=n;i++){
pos[a[i]].push_back(i);
}
for(int i=1;i<=tot;i++){
sort(pos[i].begin(),pos[i].end());
l[i]=pos[i][0];r[i]=*pos[i].rbegin();
}
for(int i=1;i<=tot;i++){
if(l[i]>r[i-1]){
dp[r[i]]=dp[r[i-1]]+pos[i].size();
start[i]=start[i-1];
}else{
dp[r[i]]=pos[i].size();
start[i]=l[i];
}
}
int ans=0;
for(int res,i=1;i<=n;i++){
res=0;
if(dp[i]){
int j=start[a[i]];
int L=a[j],R=a[i];
res+=dp[i];
if(L>1)
res+=upper_bound(pos[L-1].begin(),pos[L-1].end(),j-1)-pos[L-1].begin();
if(R<tot)
res+=pos[R+1].end()-lower_bound(pos[R+1].begin(),pos[R+1].end(),i+1);
}else{
int L=a[i],R=a[i]+1;
res+=upper_bound(pos[L].begin(),pos[L].end(),i)-pos[L].begin();
if(R<=tot)
res+=pos[R].end()-lower_bound(pos[R].begin(),pos[R].end(),i+1);
}
ans=max(ans,res);
}
cout<<n-ans<<endl;
}
}