2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序
Posted Alex_996
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序相关的知识,希望对你有一定的参考价值。
Ideas
题目中给出了两种操作:
- 当 pi = 0 时,表示将 a1, a2, · · · , aqi 降序排列;
- 当 pi = 1 时,表示将 aqi , aqi+1, · · · , an 升序排列。
按照题目暴力排序应该可以骗一点分,但如果想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]:
- p=0,q=3,即1~3位置降序排,变成了[3, 2, 1, 4, 5, 6, 7, 8, 9]
- p=1,q=7,即7~9位置升序排,不发生变化,还是[3, 2, 1, 4, 5, 6, 7, 8, 9]
- p=0,q=6,即1~6位置降序排,变成了[6, 5, 4, 3, 2, 1, 7, 8, 9]
- p=1,q=4,即4~9位置升序排,变成了[6, 5, 4, 1, 2, 3, 7, 8, 9]
- p=0,q=5,即1~5位置降序排,变成了[6, 5, 4, 2, 1, 3, 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,只取最大的 q
q = 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,只取最小的 q
q = 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, n
for i in range(len(seq)):
if seq[i][0] == 0: # 前缀降序
while right > seq[i][1] and left <= right:
nums[right - 1] = k # 从后往前设置
right -= 1
k -= 1
else: # 后缀升序
while left < seq[i][1] and left <= right:
nums[left - 1] = k # 从前往后设置
left += 1
k -= 1
if left > right:
break
if len(seq) % 2: # 最后一次操作为前缀降序
while left <= right:
nums[left - 1] = k
left += 1
k -= 1
else: # 最后一次操作为后缀升序
while left <= right:
nums[right - 1] = k
right -= 1
k -= 1
print(' '.join(map(str, nums)))
以上是关于2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序的主要内容,如果未能解决你的问题,请参考以下文章
2021年第十二届蓝桥杯 - 省赛 - C/C++大学C组 - D.相乘
2021年第十二届蓝桥杯 - 省赛 - C/C++大学A组 - D.路径
2021年第十二届蓝桥杯 - 省赛 - C/C++大学B组 - I.双向排序