1. 程式人生 > >BZOJ 2457 - 雙端佇列 - [思維題]

BZOJ 2457 - 雙端佇列 - [思維題]

題目連結:https://www.lydsy.com/JudgeOnline/problem.php?id=2457

Description
Sherry現在碰到了一個棘手的問題,有N個整數需要排序。
Sherry手頭能用的工具就是若干個雙端佇列。
她需要依次處理這N個數,對於每個數,Sherry能做以下兩件事:
1.新建一個雙端佇列,並將當前數作為這個佇列中的唯一的數;
2.將當前數放入已有的佇列的頭之前或者尾之後。
對所有的數處理完成之後,Sherry將這些佇列排序後就可以得到一個非降的序列。

Input
第一行包含一個整數N,表示整數的個數。接下來的N行每行包含一個整數Di,其中Di表示所需處理的整數。

Output
其中只包含一行,為Sherry最少需要的雙端佇列數。

Sample Input

6

3

6

0

9

6

3

Sample Output

2

HINT
100%的資料中N≤200000。

 

題解:

假設原序列 $D$ 排序後的序列為 $E$。換句話說,我們就是要將 $E$ 分成若干段,每一段對應原來的一個雙端佇列。

現在考慮如何切割 $E$,假設現在我們分割出了其中某一段 $S_1 \sim S_n$,那麼其中必然有一個數 $S_k(1 \le k \le n)$ 是第一個進入佇列的。

那麼 $S_1 \sim S_{k-1}$ 這些數在 $D$ 中的出現順序必須是降序的。換句話說,在 $S_1 \sim S_{k-1}$ 中,$S_1$ 必須是最後一個在 $D$ 中出現的。這是很顯然的,它作為最小的,顯然必須最後一個push進雙端佇列的隊首。

同樣的道理,$S_{k+1} \sim S_n$ 這些數在 $D$ 中的出現順序必須是升序的。$S_n$ 它作為最大的一個,必須最後一個push進雙端佇列的隊尾。

也就是說,在 $E$ 中儘可能少地分割出若干段,每段都滿足其內部的數在 $D$ 中的出現順序是先降後升的。這個可以用貪心策略求最少段數。

另外要考慮的一個問題是,同一個數可能在 $D$ 中不同位置出現,那麼我們應當讓他們在 $E$ 中有一個合適的順序,使得分割產生的段數儘量少。這個可以做個分類討論。

 

AC程式碼:

#include<bits/stdc++.h>
using namespace
std; typedef pair<int,int> pii; const int maxn=2e5+10; int n; pii a[maxn]; int tot; pii bnd[maxn]; int main() { cin>>n; for(int i=1;i<=n;i++) { cin>>a[i].first; a[i].second=i; } sort(a+1,a+n+1); tot=0; for(int i=1;i<=n;i++) { //printf("val=%d pos=%d\n",a[i].first,a[i].second); if(i==1 || a[i].first!=a[i-1].first) { bnd[tot].second=a[i-1].second; bnd[++tot].first=a[i].second; } } bnd[tot].second=a[n].second; //for(int i=1;i<=tot;i++) printf("[%d,%d]\n",bnd[i].first,bnd[i].second); int ans=0, pre=maxn, up=1; for(int i=1;i<=tot;i++) { if(up) { if(pre<bnd[i].first) pre=bnd[i].second; else pre=bnd[i].first, up=0, ans++; } else { if(pre>bnd[i].second) pre=bnd[i].first; else pre=bnd[i].second, up=1; } } cout<<ans<<endl; }