【算法进阶指南】双端队列 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;
}
posted @ 2020-10-31 19:27  Valk3  阅读(163)  评论(0编辑  收藏  举报