題面描述:
題目描述
給定一個長度爲N的數列Ai。
你可以對數列進行若干次操作,每次操作可以從數列中任選一個數,把它移動到數列的開頭或者結尾。
求最少經過多少次操作,可以把數列變成單調不減的。“單調不減”意味着數列中的任意一個數都不大於排在它後邊的數。
輸入格式
第一行是一個正整數N。
第二行是N個正整數Ai。
輸出格式
輸出一個整數,表示最少需要的操作次數。
樣例輸入
5
6 3 7 8 6
樣例輸出
2
數據範圍與約定
對於30%的數據,滿足1≤n≤10。
對於60% 的數據,滿足1≤n≤1000。
對於100% 的數據,滿足1≤n≤1000000,1≤Ai≤1000000。
這道題相當玄妙(鑑於數據的水)
當我聽到有人這道題直接交了最小上升子序列就拿了50分
這讓我悲傷我想了好久的如何處理一個數出現多次。。
本來我也覺得最小上升子序列沒有問題,但是發現樣例多次沒有過之後就。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
但是,中心思想永遠是,維護一個序列,除了這個序列的數以外的所有的數都要移動,我先是覺得是子序列,子串,然後都輕易的找出了反例。
最長上升子序列:只要一個數出現了兩次肯定錯的。
最長上升子串:1 3 4 2 5 最小上升子串是3 4 或者2 5,但只要移動兩次,。
然後我聰明的同桌指出了我的最大來連續子串 的錯誤,這道題,應該是找一個最長連續子序列。
連續即離散化後的序號是連續的。
(離散化emmm,我舉個例子,1 8 9 100,可以離散化爲 1 2 3 4)
比如說上一個 1 3 4 2 5,最小連續子序列是多少呢?
3 4 5
所以答案是2
但是這道題很噁心的地方在於,某個數可以出現多次,所以離散化有點特殊。
for (int i=1; i<=1000000; i++)
{
if (c[i]>0)
{
e[i]=x;
d[i]=++t;
x=t;
t+=c[i]-1;
}
}
數組解釋:
c表示桶排序每一個數字出現的次數,以便離散化以及後面統計重複數字的出現
d表示每一個數在離散化數組中的序號
e表示a[i]前一個數值的離散化序號。 譬如 8 8 10,e[10]=8;
譬如說當前這個數是a[i], 的區別是什麼呢?
比如說
7 8 8 10
—— 8
—— 9
這個離散化結合方程:
for (int i=1; i<=n; i++)
{
f[d[a[i]]][0]++;
f[d[a[i]]][1]=max(f[d[a[i]]][1]+1,max(f[e[a[i]]][0]+1,f[d[a[i]-1]][1]+1));
if (f[d[a[i]]][0]==c[a[i]]) f[d[a[i]]][0]=f[d[a[i]]][1];
f[d[a[i]]][2]=max(f[d[a[i]]][1],f[d[a[i]]][2]+1);
ans=max(ans,f[d[a[i]]][2]);
}
表示離散化後序號爲i的數處於j狀態時的最長序列長度,就是說作爲開頭也可以作爲一個前綴,不僅可以從當前這個數開始,也可以從之前作爲某個序列的結尾開始。
j可以0(作爲開頭),1(作爲中間繼承的過程),2(作爲結尾)
因爲無論如何,哪怕a[i]是否重複出現,作爲開頭都可以從前面出現過的a[i]或者0開始
作爲過程,如果是已經重複出現過,那麼可以延長,也可以從上一個數字繼承過來,
可以直接在過程中結尾,也可以作爲本身上一個結尾的數字下來結尾(或者也可以說直接結尾。。。)。
這樣子只有80.
加了那句我沒有解釋的話就在洛谷上過了。。。。(根據某個數據做的特判)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000010;
int a[maxn],e[maxn],c[maxn],d[maxn],f[maxn][4];
inline int read()
{
int X=0,w=0; char c=0;
while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
return w?-X:X;
}
int main()
{
//freopen("change.in","r",stdin);
//freopen("change.out","w",stdout);
int n;
n=read();
for (int i=1; i<=n; i++)
{
a[i]=read();
c[a[i]]++;
}
int x=0,t=0;
for (int i=1; i<=1000000; i++)
{
if (c[i]>0)
{
e[i]=x;
d[i]=++t;
x=t;
t+=c[i]-1;
}
}
int ans=0;;
for (int i=1; i<=n; i++)
{
f[d[a[i]]][0]++;
f[d[a[i]]][1]=max(f[d[a[i]]][1]+1,max(f[e[a[i]]][0]+1,f[d[a[i]-1]][1]+1));
if (f[d[a[i]]][0]==c[a[i]]) f[d[a[i]]][0]=f[d[a[i]]][1];
f[d[a[i]]][2]=max(f[d[a[i]]][1],f[d[a[i]]][2]+1);
ans=max(ans,f[d[a[i]]][2]);
}
cout<<n-ans;
}
這個程序是錯的。。。但是在洛谷上過的。。。。。
爲什麼錯呢
數據一:
9
5 4 2 1 8 6 7 8 10
ans:5
10
2 2 1 1 3 3 4 4 5 5
ans:2
。。這些數據過不了但是洛谷ac了。。。
等我一下我過會兒寫正解。。
正確解法1
…………爲什麼是錯的呢,是因爲我們沒有辦法處理重複的數字的每次承載的。也就是說,我們每次去維護的作爲重複出現的數字的開頭,只會是這個數字出現的次數,所以,我們去設置一個狀態j=3,去記載以該數字結尾的最大前綴,每次都要更新,如果是當前這個狀態值是3,那麼可以從以這個狀態爲結尾的上次出現數值爲更新,
直到這個數字出現的最後一次,去更新以這個數字爲結尾的最大前綴。
而我們需要的以這個數字爲開頭的狀態,即最大前綴,而非這個數字單純的出現次數。
後來我又發現了 包含了所有 ,根本不需要 。
#include<bits/stdc++.h>
using namespace std;
int n,a[1000005],c[1000005],d[1000005],e[1000005],f[1000005][4],t,ans,maxx;//a記錄原數組,c是桶,d[i]是值爲i的數的編號,e[i]是值爲i的數的前一個數的編號
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
c[a[i]]++;
maxx=max(a[i],maxx);
}
int x=0;
for (int i=1;i<=1000000;i++)
{
if (c[i]>0)
{
e[i]=x;
d[i]=++t;
x=t;
t+=c[i]-1;
}
}
for (int i=1;i<=n;i++)
{
c[a[i]]--;
f[d[a[i]]][0]=f[d[a[i]]][0]+1;
f[d[a[i]]][1]=max(f[d[a[i]]][1]+1,f[e[a[i]]][0]+1);
f[d[a[i]]][2]=max(f[d[a[i]]][1],f[d[a[i]]][2]+1);
if (f[d[a[i]]][3]==0) f[d[a[i]]][3]=f[d[a[i]]][2];
else f[d[a[i]]][3]++;
if (c[a[i]]==0) f[d[a[i]]][0]=f[d[a[i]]][3];
ans=max(ans,f[d[a[i]]][2]);
}
cout<<n-ans;
}
正確解法2:
在這道題裏,我們可以將該序列中的任何一個數放到後面或前面,而我們要這個操作次數最少,那麼我們就要使保持不動的點最多,而如果任意兩個點(數)不動,那麼就不能將任何原本不在該兩個數中間的數插入這兩個數中間。
因此,我們就要求出一個連續的最長子序列。
何爲連續?
就是說該序列必須是最後的結果序列的子串
即,如果該數的前一個不同的數(pre)和後一個不同的數(next)都在之中,那麼這個數必須全部都在pre和next中間。
所以我們可以一個數一個數考慮,並將可以存在的數放在單調隊列中
條件1:該隊列中的數在保證不減的同時保證原序號遞增
條件2:同時如果該序列中將要放入b這個數,而a小於b,若大小等於a的數並沒有全部在隊列裏,那麼小於a的數都不能在隊列裏
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000010],ans;
deque<int> q;//雙端隊列模擬單調隊列
vector<int> b[1000010];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[a[i]].push_back(i);//將同一個大小的數的id記錄下來
//(因爲從前向後讀入所以保持單調)
m=max(m,a[i]);//最大的數是多少
}
for(int i=1;i<=m;i++){//從小到大將數加入單調隊列
int lt=b[i].size();
for(int j=lt-1;j>=0;j--){
int k=b[i][j];//拿出大小爲i,id在i中大小爲第j大及以後的數中最靠前的數的id
//只要該數能放入隊列中,序號比該數大,大小與它相同的都能放入
while(!q.empty()&&q.back()>k){//把隊列處理成能將k放入的狀態
while(q.size()>=1&&a[q.front()]<a[q.back()]) q.pop_front();
//這個while保證條件二
q.pop_back();
//因爲條件一,所以序號越大的數越在隊列的後面
}
int lm=q.size();
ans=max(ans,lm+lt-j);//更新答案
}
for(int j=0;j<lt;j++) q.push_back(b[i][j]);//將該數加入隊列
}
printf("%d",n-ans);//答案是總長減去最長序列的長度
}
這是我同桌的博客可以互爲註解
lzj之博客