题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2457
假如按照读入顺序处理这一堆数,比如对于 1 3 4 999 6 ,当处理到999这个数的时候我不清楚999是否应该放入前面由 1,3,4 组成的队列中。
因为最后的队列需要拼接起来形成一个非降数列,所以队列中应该要单调,然后队列和队列之间也需要能拼起来形成单调队列,当999放入1 3 4 这个队列的时候,此时假如后面出现小于999大于1的数,则不可能最后能形成非降数列。
换句话说,对于相对较大的数,假如在前面便形成双端队列,那么后面就有可能形不成非降序列。
所以,我们需要将大的数放到后面,先处理相对较小的数,怎么做呢
就是将原序列排序,然后再将原下标带入
比如对于数列A:3 2 1 999 7 排序之后数列B: 1 2 3 7 999 然后带入原下标,就变成数列C:3 2 1 5 4
可以看到我们从左到右处理的话,大的就放在后面处理了,这也符合我们的做法,原问题就等价于在C中用最小的双端队列划分使得满足题目条件
对于原数列A,要形成单调双端队列的条件除了本身原数列就单调,对于原数列先递增再递减或者先递减再递增都有可能可以形成单调的双端队列,那么就以先递减再递增的方法来说。
对于先递减再递增的原数列要形成不减双端队列(从左往右读入数),要求递减区域的最大值小于递增区域的最大值。
比如 3 2 1 4 5 可以变成 1 2 3 4 5 而 5 4 1 2 3 则不可以
然后看我们的数列C,如何将数列A中的不减双端队列条件转化为数列C的条件呢
首先,数列C本来就是递增的数列,因此假如我们按遍历顺序找数就可以保证后面的数比前面的大,也就是前面的数列A的条件:要求递减区域的最大值小于递增区域的最大值。
接着,数列A中的递减区域,就是下标越大数越小,则对应数列C也应该是递减区域
数列A中的递增区域,对应数列C就也应该是递增区域了
最后,假如数列A中有重复的数字,因为最后我们要尽可能少的不减数列,所以将所有重复数字都放在一起应该是一个贪心的最优选择。
代码:
#include<bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 7;
struct node {
int digit, rk;
}num[maxn];
int n;
vector<int> blo[maxn];
bool cmp(node a, node b) {
return (a.digit^b.digit) ? a.digit < b.digit : a.rk < b.rk;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%d", &num[i].digit);
num[i].rk = i;
}
sort(num + 1, num + 1 + n, cmp); //排序,数字小的且出现早的排前面
int cnt = 0;
for (int i = 1; i <= n; i++) {
blo[++cnt].push_back(num[i].rk); // 此时根据重复数字分块,重复则在一块
while (num[i].digit == num[i + 1].digit) {
blo[cnt].push_back(num[++i].rk);
}
}
int flag = 0, ans = 1, las = INF;
//flag = 0 : decrease
for (int i = 1; i <= cnt; i++) {
int siz = blo[i].size();
if (flag) { // 根据递减或者递增将连续出现的数字以递减序或者递增序放入,使得能够取得最有条件
if (las < blo[i][0]) las = blo[i][siz-1];
else {
flag ^= 1;
ans++;
las = blo[i][0];
}
}
else { //decrease
if (las > blo[i][siz - 1]) las = blo[i][0];
else {
flag ^= 1;
las = blo[i][siz - 1];
}
}
}
cout << ans << endl;
return 0;
}