給出一個長度為n的數列\(\{a_i\}\),從左至右進行操作,假設是對第i個數操作,你有以下選擇
- 創建一個新的雙端隊列,並將\(a_i\)入隊
- 入隊一個已有的雙端隊列
最后需要滿足所有的雙端隊列會有一種方案首尾相接形成一個新的數列,這個數列單調遞增,\(n\leq 2\times 10^5\)。
解
從答案的角度思考問題,最后的數列是一個單調遞增的數列,雙端隊列類似這個數列中的一個一個區間,於是又像是一道區間划分問題,很快反應出這個問題的一個通用性質,也就是一般可以遞推。
先構造數列\(\{b_i\}\),令\(b_i=a_i\),再把\(\{b_i\}\)按從小到大排序,構造一個\(\{c_i\}\),\(c_i\)表示\(b_i\)在\(\{a_i\}\)中出現的位置。
現在我們就只要合法地將\(\{b_i\}\)划分成盡可能少的區間,於是根據\(\{c_i\}\),我們發現把\(i\)作為自變量,\(c_i\)作為因變量,那么此時形成一個有一個最低點,最低點兩端的圖像都是單調的圖像,這一段區間就可以作為一個雙端隊列中的元素,如圖。
把\(c_i\)看做入隊的時間的話,原因在於最低點\(c_i\)表示入隊早,而\(b_i\)前的元素都要比\(b_i\)小,而且后入隊,因此放在雙端隊列前面,而\(b_i\)后的元素都要比\(b_i\)大,入隊時間靠后,因此后來可以放在雙端隊列后面,於是\(b_i\)的這樣根據\(c_i\)的合法划分,恰恰對應了一個雙端隊列的操作,再進一步,你會發現實際上我們的考慮角度是針對一個雙端隊列應該怎么放,而不是一開始傳統的思路,一個數字該放在哪個雙端隊列。
於是問題就變成尋找這樣一個圖形的最少的個數的問題,而又注意到\(\{b_i\}\)相同的一段元素時,它們的\(\{c_i\}\)中的元素可以自由交換,我們不妨將之看成一個整體,內部按\(\{c_i\}\)排序,也就是剛開始\(\{b_i\}\)排序的時候先按大小為關鍵字排序,相同時再按位置排序,於是它可以形成一下兩幅圖的圖形,記這段相同的元素的位置為\(i\sim j\),
容易知道圖形的最大值為\(c_j\),最小值為\(c_i\),設l表示前\(i-1\)個元素的最后一個區間的最后一個元素的所對應的因變量,設j為前i-1個元素最后一個區間所需要表現的狀態(0表示接下來這個函數要表現遞減,1表示要遞增),於是我們可以進行分類討論,如果
- j=0
\(c[j]<l\),那么此時區間\(i\sim j\)可以接上前面的函數圖像保證圖像合法,仍然保證函數在遞減,對應了這兩個區間可以合並成一個區間,對應下圖
\(c[j]>l\),此時區間\(i\sim j\)可以接上前面的函數,但是不能保證函數一直單調遞減,於是后面一截需要單調遞增
- j=1
\(c[i]>l\),於是后面的函數可以接上前面的函數,仍然保持單調遞增。
\(c[i]<l\),於是后面的函數無論如何也接不上前面的函數,故需要開一個新的區間。
因此,我們就可以在\(O(nlog(n))\)解決這道題目。
最后,注意到這是一個數列的問題,不妨總結一下數列問題的解決辦法
從性質上看,一可以根據逆序對個數和奇偶性與問題的關系,二可以把\(a_i\)排序構造一個新的數列\(\{b_i\}\),再構造一個\(\{c_i\}\)記錄\(\{b_i\}\)第i個元素在\(\{a_i\}\)中出現的位置,三是構造數列\(\{b_i\}\),令\(b_i=a_i-i\),四是最長上升子序列的長度和個數與原問題的關系。
從角度入手可以考慮第i個位置,前i個位置,相鄰位置,常用辦法是微擾法,集合問題可以通過排序轉成數列問題。
代碼很簡單,思維很難,多看幾遍就可以了,實在看不懂可以問我。
參考代碼:
#include <iostream>
#include <cstdio>
#include <algorithm>
#define il inline
#define ri register
#define Size 200050
#define intmax 0x7fffffff
using namespace std;
struct D{
int a,b;
il bool operator<(const D&x)const{
return a==x.a?b<x.b:a<x.a;
}
}d[Size];
il void read(int&);
int main(){
int n,ans(0);read(n);
for(int i(1);i<=n;++i)
read(d[i].a),d[i].b=i;
sort(d+1,d+n+1),d[n+1].a=intmax;
for(int i(1),j,k(1),l(intmax);i<=n;i=j+1){
j=i;while(d[j].a==d[j+1].a)++j;
if(k){
if(d[i].b>=l)l=d[j].b;
else ++ans,k^=1,l=d[i].b;
}
else{
if(d[j].b<=l)l=d[i].b;
else k^=1,l=d[j].b;
}
}printf("%d",ans);
return 0;
}
il void read(int &x){
x^=x;ri char c;while(c=getchar(),c==' '||c=='\n'||c=='\r');
ri bool check(false);if(c=='-')check|=true,c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
if(check)x=-x;
}