F2. Flying Sort (Hard Version)
题目大意:
给你一个大小是n的序列,有两种操作可以进行:
- 选一个数字放在最前面
- 选一个数字放在最后面
这个序列可能含有相同的数字,问最少的操作让这个序列变成一个不递减的序列。
题解:
其实这个题目难度不大,思考一会就知道要怎么求,但是这个dp的形式我很少遇到,所以记录一下。
容易得到,最多是n次一定可以做到,那么接下来就是考虑选择一个最长子序列,使得保持这个子序列不变,其他元素经过操作之后变成一个不递减的序列。
这种题目,首先要做的就是研究这个操作的实质,容易发现,如果选择的这个子序列中间需要插入值,那么肯定是不符合要求的,所以要找的这个子序列就是一个大小关系是连续的。
但是由于这个可能有重复的数字,所以让这个dp的难度变大了,接下来就是具体的dp定义和dp转移,在代码里面。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
int last[maxn],cnt[maxn],fir[maxn],num[maxn];
int a[maxn],dp[maxn],v[maxn];
/**
dp[i] 表示到位置i的最长的序列
last[i] 表示上一个大小是i的位置
fir[i] 表示第一个i出现的位置
num[i] 表示i的数量
cnt[i] 表示i到目前为止出现的次数
转移肯定只能向比自己小1的转或者和自己相等的在前面的转移:
第一种转移:如果x不是第一个,那么可以往前面的转移,dp[i]=dp[last[x]]+1
第二种转移:如果x-1全部都出现了,那么往x-1的第一个转移 dp[i] = dp[fir[x-1]]+cnt[x-1];
第三种转移:如果x-1不是全部都出现了,那么x-1必须当第一个,所以 dp[i] = cnt[x-1]+1
**/
void init(int n){
for(int i=0;i<=n+10;i++){
dp[i] = last[i] = fir[i] = num[i] = cnt[i] = 0;
}
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int n;
scanf("%d",&n);
init(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
v[i] = a[i];
}
sort(v+1,v+1+n);
int len = unique(v+1,v+1+n)-v-1;
for(int i=1;i<=n;i++) {
a[i] = lower_bound(v+1,v+1+len,a[i])-v;
num[a[i]]++;
}
// printf("sss
");
int ans = 0;
for(int i=1;i<=n;i++){
int x = a[i];
dp[i] = max(dp[i],cnt[x-1]+1);
if(last[x]) dp[i] = max(dp[last[x]]+1,dp[i]);
if(cnt[x-1]==num[x-1]) dp[i] = max(dp[i],dp[fir[x-1]]+cnt[x-1]);
cnt[x]++,last[x] = i;
if(!fir[x]) fir[x] = i;
ans = max(ans,dp[i]);
}
printf("%d
", n-ans);
}
}