1. 程式人生 > 實用技巧 >【演算法進階指南】雙端佇列 DP+思維

【演算法進階指南】雙端佇列 DP+思維

題目連結

題意

Sherry現在碰到了一個棘手的問題,有N個整數需要排序。Sherry手頭能用的工具就是若干個雙端佇列。
她需要依次處理這N個數,對於每個數,Sherry能做以下兩件事:

  1. 新建一個雙端佇列,並將當前數作為這個佇列中的唯一的數;

  2. 將當前數放入已有的佇列的頭之前或者尾之後。

對所有的數處理完成之後,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;
}