问题描述
思路
这道题对于我这种新手小白也挺难的,看 AcWing 看了好久才弄明白,话不多说,直接上解析!
首先我们要考虑下面的几种情况:
(1)操作一(q 为 0 降序)
当出现连续的操作一时,我们只需找到其中跨越区间最大的那一个即可,因为小于它的其它区间都包含在最长的这个区间里,就不用重复的去完成降序操作了。
(2)操作二(q 为 1 升序)
这个操作必须在前面进行了操作一的前提下才能进行,因为如果前面没有操作一,直接进行操作二没有意义,原本的序列就是升序的。
同样,如果出现连续的情况,要找到区间最长的那个操作。
(3)操作一和操作二交替进行
如果操作一和操作二不是连续的,并且你当前的操作的区间要大于之前的操作一或者操作二,那么就要将当前操作的前面两个操作删除,因为当前操作会覆盖掉之前的操作。例如,当前操作一比上次操作一的区间要大,那我就将上次操作一给删除,但是两次操作二就会相邻,所以就要将操作一和操作二一同删除。
经过上面三种情况的排除后,我们可以发现一个规律,我的所有操作中,我的第一次操作一和操作二一定时所有操作一和操作二中包含区间最大的,因为比他小的之前的区间或相邻的区间都删除了,所以该操作之后的不相邻操作区间会不断的减小,并且是操作一和操作二交错进行的。
这样,我们只用改变相邻操作一和操作二重叠部分的次序即可,通过不断地操作,中间公共区域会变得越来越小即左边界和右边界会逐渐往中间去靠,直至左右边界相遇。
所以,我们每次操作都会改变左边界和右边界,而左边界的左边和右边界的右边就是已经排好序的部分,通过不断的缩小中间区域来逐渐填满整个列表。
代码
#include<bits/stdc++.h> using namespace std; #define x first #define y second typedef pair<int, int> PII; const int N = 100010; PII stk[N]; int ans[N]; int m, n; int main() { scanf("%d%d", &n, &m); int top = 0; while (m--) { int p, q; scanf("%d%d", &p, &q); //如果是操作一 if (!p) { //如果连续执行操作一,则只保留右边界值最大的那个 while (top && stk[top].x == 0) q = max(q, stk[top--].y); //如果当前的操作右边界要比上一次操作一的右边界值大,则删除前面的操作一和操作二 while (top >= 2 && stk[top - 1].y <= q) top -= 2; stk[++top] = { 0,q }; //保存本次最佳的值 } else if (top) //操作二之前必须要有操作一,不然没有意义 { //如果连续执行操作二,则只保留左边界值最小的那个 while (top && stk[top].x == 1) q = min(q, stk[top--].y); //如果当前的操作左边界要比上一次操作二的左边界值小,则删除前面的操作一和操作二 while (top >= 2 && stk[top - 1].y >= q) top -= 2; stk[++top] = { 1,q }; //保存本次最佳的值 } } int k = n, l = 1, r = n; for (int i = 1; i <= top; i++) { if (stk[i].x == 0) //如果是操作一 while (r > stk[i].y && l <= r) ans[r--] = k--; //右边界左移,并赋值(升序) else while (l < stk[i].y && l <= r) ans[l++] = k--; //左边界右移,并赋值(降序) if (l > r) break; } //如果 l 和 r 没有相遇即中间仍有空的区域,则需要根据最后一次操作填满 if (top % 2 == 0) //如果最后一次操为作一,则需要将中间按降序填满 while (l <= r) ans[r--] = k--; else //如果最后一次为操作二,则需要将中间按升序填满 while (l <= r) ans[l++] = k--; for (int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0; }