题目描述
给定一个长度为N的数列Ai。
你可以对数列进行若干次操作,每次操作可以从数列中任选一个数,把它移动到数列的开头或者结尾。
求最少经过多少次操作,可以把数列变成单调不减的。“单调不减”意味着数列中的任意一个数都不大于排在它后边的数。
输入格式
第一行是一个正整数N。
第二行是N个正整数Ai。
输出格式
输出一个整数,表示最少需要的操作次数。
输入输出样例
输入 #1复制
5 6 3 7 8 6
输出 #1复制
2
说明/提示
对于30%的数据,满足1≤n≤10。
对于60% 的数据,满足1≤n≤1000。
对于100% 的数据,满足1≤n≤1000000,1≤Ai≤1000000。
分析:
本题的本质就是求原序列的一个最长无缝上升子序列长度,再将原序列长度减去该长度即是答案。
先来解释一下什么叫无缝,无缝即这整个序列中没有数值大小在这一段序列中的数,比如看这一个序列 5 3 4 7 8
3 4 7 8是这段序列的一个最长上升子序列,但原序列中有一个5, 4<5<7,所以这一段序列不能称为无缝,最长无缝子序列应该是5 7 8,因为没有原序列中没有一个在5和7之间的数。它的长度为3,所以答案应该是5-3=2
但如果原序列变成了5 3 4 7 8 6,那么它的最长无缝上升子序列就是3 4或是5 6或是7 8,长度为2,答案是6-2=4
为什么答案可以这样算呢?
因为一个最长无缝上升子序列就是我们在操作时不动的部分,所以我们要操作的数就=原序列长度-最长无缝上升子序列长度
为什么要无缝呢
因为如果有缝,在我们排序时就要将一个在其中间的数插入其中,就不是操作时不动的部分了
好了,现在的问题就是如何求无缝上升子序列了
在求该无缝上升子序列时我们关注的是该数是否能接上这段序列中刚好小于它的数,而不去关注其实际数值,这让我们想到了离散化(我看过的比较好懂的离散化)
将序列离散之后我们就顺着扫一遍离散过的序列,用数组a[i]表示离散过的序列,我们用数组f[i]表示以原序列第i个数结尾的最长无缝上升子序列,数组T【a[ i ] 】表示以a[i]结尾的最长无缝上升子序列
对于每个离散过的数如果之前没有被扫描过,那么就以它结尾的最长无缝上升子序列就是刚好小于它的数结尾的最长上升子序列长度加一(即f[ i ]= T[ a [ i ] - 1 ]+ 1(因为离散过,a[i]-1就是刚好小于a[i]的数))。如果这个数之前已经出现过,那么就将它的值加一(f [ i ]=T [a [ i ] ] + 1 )
#include <bits/stdc++.h>
#include <vector>
#include <algorithm>
using namespace std;
#define N 1000001
int n;//数组长度
int ans;//最长无序序列
int len;
int dis[1000001],f[1000001],w[1000001];//离散数组,标记过的数直接加入到序列中去
int main(){
cin>>n; //数组长度
for(int i=1;i<=n;++i){
cin>>w[i];
dis[i]=w[i];
}
sort(dis+1,dis+n+1);
len=unique(dis+1,dis+n+1)-dis-1; //去掉重复数据
for(int i=1;i<=n;++i){
w[i]=lower_bound(dis+1,dis+len+1,w[i])-dis;//离散化
if(!f[w[i]])f[w[i]]=f[w[i]-1]+1;//未标记的数进行操作
else f[w[i]]=f[w[i]]+1;//标记过的数直接加入序列
ans=max(ans,f[w[i]]);//每次将最大的无缝不降子序列记录
}
cout<<n-ans<<endl;//输出
return 0;
}