2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序
2021年第十二屆藍橋杯 - 省賽 - C/C++大學B組 - I.雙向排序
Ideas
題目中給出了兩種操作:
按照題目暴力排序應該可以騙一點分,但如果想AC,就需要優化算法。可以根據測試用例的范圍:1 ≤ n, m ≤ 100000,估計一下時間復雜度要控制在O(nlog n)。
首先對于連續的p=0,即:pi=0 qi=a;pi+1=0 qi+1=b。如果b>a,那么(pi, qi)的操作將無效,因為(pi+1, qi+1)已經將(pi, qi)的范圍包含了。同理,如果pi+2=0; qi+2=c,而b>c,那么pi+2和qi+2的操作也將無效。
然后對于連續的p=1,即:pi=1 qi=a;pi+1=1 qi+1=b。同理,如果a<b,那么(pi+1, qi+1)的操作將無效,因為(pi, qi)已經將(pi+1, qi+1)的范圍包含了。
因此我們可以總結出,對于連續的p=0和p=1,只需要分別保留q最大和q最小的那次操作即可。
有了上面的簡化之后,整個操作序列其實就被壓縮成了p=0和p=1的交替操作,由于一開始的序列是升序排列的,所以第一個有效操作肯定是pi = 0,讓我們將某一個前綴降序排列。
我們舉個例子來分析一下,令n=9,即[1, 2, 3, 4, 5, 6, 7, 8, 9]:
通過這個例子我們可以發現一些規律,有些數字的位置被固定下來了,基本不會變。
為了會這樣呢?我們分析一下。
對于第1次有效操作,假設是將[1, x]降序排列,由于最初我們的數據都是升序的,所以?b∈[x+1, n] > ?a∈[0, x]。
那么之后我們對[y, n]升序排列(y<=x) 的話其實[x, n]這部分是不變的。
這是因為[y, x] ∈ [0, x] < [x+1, n],所以[x+1, n]這部分的任意值是始終大于[y, x]中的任意值。
因此我們對[y, n]升序排序的話,其實[x, n]這部分不會挪動位置,換句話說,[x, n]已經被固定下來了。
同理,[1, y]其實也被固定下來了,所以說我們不停的操作其實就是不停的從數組的兩邊向中間固定元素。
我們可以用兩個變量 left 和 right,分別表示數組的兩邊被固定下來的位置,left 不斷增加,right 不斷減小,最終數組被固定。
最后扣一下邊界,如果最后一次操作是前綴降序,那相當于確定一個[x, n],我們手動把一個[i, x]確定下來就可以了,反之亦然。
Code
C++
#include <iostream>using namespace std;const int N = 100010; pair<int, int> stk[N]; int ans[N];int main() {int n, m, top = 0;cin >> n >> m;while (m--) {int p, q;cin >> p >> q;if (p == 0) {while (top && stk[top].first == 0) {q = max(q, stk[top--].second);}while (top >= 2 && stk[top - 1].second <= q) {// 如果當前操作比上一次相同操作的范圍要大,那此次操作的前兩次操作都將被無效化 top -= 2;}stk[++top] = {0, q};} else if (top) {while (top && stk[top].first == 1) {q = min(q, stk[top--].second);}while (top >= 2 && stk[top - 1].second >= q) {// 如果當前操作比上一次相同操作的范圍要大,那此次操作的前兩次操作都將被無效化 top -= 2;}stk[++top] = {1, q};}}int left = 1, right = n, k = n;for (int i = 1; i < top + 1; i++) {if (stk[i].first == 0) {while (right > stk[i].second && left < right + 1) {ans[right--] = k--;}} else {while (left < stk[i].second && left < right + 1) {ans[left++] = k--;}}if (left > right) {break;}}if (top % 2) {while (left < right + 1) {ans[left++] = k--;}} else {while (left < right + 1) {ans[right--] = k--;}}for (int i = 1; i < n + 1; i++) {cout << ans[i] << " ";}return 0; }Python
if __name__ == '__main__':n, m = map(int, input().split())nums = [i + 1 for i in range(n)]seq = [] # 用于存儲操作序列for _ in range(m):p, q = map(int, input().split())if p == 0:while seq and seq[-1][0] == 0: # 如果是連續的 p = 0,只取最大的 qq = max(q, seq[-1][1])seq.pop()while len(seq) > 1 and seq[-2][1] <= q: # 如果此次前綴降序的右邊界大于上一次前綴降序的右邊界,可以省略在此之前的兩次操作seq.pop()seq.pop()seq.append((0, q))elif seq: # seq 不為空保證這是有一個 p = 0 之后的 p = 1 操作while seq and seq[-1][0] == 1: # 如果是連續的 p = 1,只取最小的 qq = min(q, seq[-1][1])seq.pop()while len(seq) > 1 and seq[-2][1] >= q: # 如果此次后綴升序的左邊界小于上一次后綴升序的右邊界,可以省略在此之前的兩次操作seq.pop()seq.pop()seq.append((1, q))k, left, right = n, 1, nfor i in range(len(seq)):if seq[i][0] == 0: # 前綴降序while right > seq[i][1] and left <= right:nums[right - 1] = k # 從后往前設置right -= 1k -= 1else: # 后綴升序while left < seq[i][1] and left <= right:nums[left - 1] = k # 從前往后設置left += 1k -= 1if left > right:breakif len(seq) % 2: # 最后一次操作為前綴降序while left <= right:nums[left - 1] = kleft += 1k -= 1else: # 最后一次操作為后綴升序while left <= right:nums[right - 1] = kright -= 1k -= 1print(' '.join(map(str, nums)))總結
以上是生活随笔為你收集整理的2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode Algorithm 4
- 下一篇: 2020年第十一届蓝桥杯 - 省赛 -