【演算法進階指南】雙端佇列 DP+思維
阿新 • • 發佈:2020-10-31
題意
Sherry現在碰到了一個棘手的問題,有N個整數需要排序。Sherry手頭能用的工具就是若干個雙端佇列。
她需要依次處理這N個數,對於每個數,Sherry能做以下兩件事:
-
新建一個雙端佇列,並將當前數作為這個佇列中的唯一的數;
-
將當前數放入已有的佇列的頭之前或者尾之後。
對所有的數處理完成之後,Sherry將這些佇列排序後就可以得到一個非降的序列。
思路
反著考慮,我們先將所有的數字排好序,然後儘可能少的分成連續的幾段,使得每段都可以出現在一個雙端佇列中。
分析一下可以知道,一個雙端佇列中的數字從頭到尾的下標一定是先遞減再遞增,或者直接遞增,或者直接遞減。
注意多個數字相同時,可以對他們的下標任意排序。
首先處理出每個數字出現的最小下標和最大下標,按照數字大小排序之後進行DP。
\(dp[i][0]\) 表示以第 \(i\) 個數字是升序的方式出現在佇列中需要的最小的佇列數。
\(dp[i][1]\) 表示以第 \(i\) 個數字是倒序的方式出現在佇列中需要的最小的佇列數。
初始化\(dp[1][1] = dp[1][0] = 1\)
具體轉移看程式碼
程式碼
#include <algorithm> #include <map> #include <queue> #include <stack> #include <stdio.h> #include <string.h> #include <vector> #define pb push_back typedef long long ll; const int inf = 0x3f3f3f3f; const int N = 3e5 + 10; const int mod = 1e9 + 7; using namespace std; vector<int> vec; map<int, int> minn, maxn; int dp[N][2]; int main() { int n; scanf("%d", &n); for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); vec.pb(x); maxn[x] = i; if (!minn[x]) minn[x] = i; } sort(vec.begin(), vec.end()); vec.erase(unique(vec.begin(), vec.end()), vec.end()); memset(dp, inf, sizeof(dp)); dp[0][0] = dp[0][1] = 1; for (int i = 1; i < vec.size(); i++) { //遇到降序時dp值才增加 dp[i][0] = min(dp[i][0], dp[i - 1][1]);//在上次遞減,這次遞增,dp值不變 if (minn[vec[i]] > maxn[vec[i - 1]]) { dp[i][0] = min(dp[i][0], dp[i - 1][0]);//在上次遞增的基礎上遞增 } else { dp[i][0] = min(dp[i][0], dp[i - 1][0] + 1);//上次遞增,這次先遞減再遞增,dp值+1 } dp[i][1] = min(dp[i][1], dp[i - 1][0] + 1);//在上次遞增,這次遞減,dp值+1 if (maxn[vec[i]] < minn[vec[i - 1]]) {//在上次遞減的基礎上再遞減,dp值不變 dp[i][1] = min(dp[i - 1][1], dp[i][1]); } else { dp[i][1] = min(dp[i][1], dp[i - 1][1] + 1);//上次遞減,這次先遞增再遞減,dp值+1 } } printf("%d\n", min(dp[vec.size() - 1][0], dp[vec.size() - 1][1])); return 0; }