2025.03.26 CW 模拟赛 B. 栈法师
B. 栈法师
题目描述
传说中存在着一扇神秘的魔法大门,它通向着无尽的魔法宝藏。但是这扇大门只对那些能够熟练运用栈法杖的魔法师敞开。
大门的密码是一个序列 \(a[1...n]\),为了解锁大门,魔法师需要将 \(a\) 中的数字输出到另一个序列 \(b\),并保证 \(b\) 是非降序的。魔法师携带了 \(k\) 根栈法杖作为解锁这扇神秘大门的工具。开始时,这些栈法杖都是空的,他可以使用三种栈法杖操作:
-
\((1, i)\):从序列 \(a\) 的末尾取出一个数字(栈的 pop 操作),并将它储存到第 \(i\) 根栈法杖中(栈的 push 操作)。
-
\((2, i)\):从第 \(i\) 根栈法杖中取出最后一个储存的数字(栈的 pop 操作),并将其添加到输出序列 \(b\) 的末尾(栈的 push 操作)。
-
\((3, i, j)\):从第 \(i\) 根栈法杖中取出最后一个储存的数字(栈的 pop 操作),并将其添加给第 \(j\) 根栈法杖(栈的 push 操作)。
注意,魔法师不能直接将序列 \(a\) 的末尾数字添加到输出序列 \(b\) 中。年轻的魔法师深思熟虑后,意识到只要他手中的栈法杖数量大于等于序列 \(a\) 的总数 \(n\),他就一定能够解锁大门。
魔法师想知道,解锁大门所需的最少栈法杖数量是多少,并且设计一个巧妙的法杖操作方案,以确保输出序列 \(b\) 是按非降序排列的。
需要找出完成解锁大门所需的最少栈法杖数量 \(k\),并且设计一个巧妙的栈法杖操作方案,以确保输出序列 \(b\) 是按非降序排列的。
思路
不难发现我们使用的栈最多不会超过 \(2\) 个, 先特判掉只需要一个栈的情况.
考场思路. 设当前应当弹出的数为 \(x\). 通过观察大样例可以发现操作可以分为以下几种
- 如果 \(x\) 依然在 \(a\) 序列中, 那么我们将 \(a\) 序列的一段后缀 \((\)直到 \(x)\) 全部压入栈 \(1\) 中, 再将 \(x\) 弹出即可.
- 如果当前 \(x\) 在栈中 \((\)这里只讨论 \(x\) 在栈 \(1\) 的情况\()\), 那么我们需要将它「头上」的数全部弹出并压入栈 \(2\) 中, 再将 \(x\) 弹出并加入 \(b\) 序列.
这样对于每一个数会操作很多次, 过不了 \(\sum n = 3 \times 10^5\) 的数据.
正解思路. 操作其实十分类似, 不过为了方便后续优化, 我们先将整个 \(a\) 序列压入栈中, 同时钦定 \(x\) 只从栈 \(1\) 弹出. 即使它在栈 \(2\) 的顶端, 我们也先将它弹出并压入栈 \(1\), 再弹出加入 \(b\) 序列. 栈 \(2\) 起一个中转作用.
那么每次需要弹出 \(x\) 时, 会有以下操作
- 将 \(x\) 上方的数全部弹出并压入栈 \(2\) 中 \((\)如果没有就不进行此操作\()\), 总操作个数为 \(n - pos_x - k\), 其中 \(pos_x\) 表示 \(x\) 在原数组中的后缀位置, \(k\) 表示在 \(x\) 上方有多少个数已经被加入 \(b\) 序列了. 最后弹出 \(x\) 后再将这些数重新压入栈 \(1\) 即可.
容易发现 \(k\) 的值可以使用树状数组进行维护, 时间复杂度就对了.
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
using namespace std;
int read() {
int x = 0; char c = getchar();
while (c < '0' or c > '9') {
c = getchar();
}
while (c >= '0' and c <= '9') {
x = x * 10 + (c & 15);
c = getchar();
}
return x;
}
void print(int x) {
if (x > 9) {
print(x / 10);
}
putchar(x % 10 + 48);
}
void print(int x, char c) { print(x), putchar(c); }
constexpr int N = 100001;
struct Node {
int v, id;
friend bool operator<(Node x, Node y) {
return x.v < y.v;
}
};
int n, a[N], tr[N], pos[N];
Node b[N];
vector<vector<int>> opt;
void update(int x, int v) {
for (int i = x; i; i -= i & -i) {
tr[i] += v;
}
}
int query(int x) {
int res = 0;
for (int i = x; i <= n; i += i & -i) {
res += tr[i];
}
return res;
}
bool check() {
int cnt = 1;
stack<int> st;
for (int i = n; i; --i) {
st.push(a[i]);
while (!st.empty() and st.top() == b[cnt].v) {
st.pop(), ++cnt;
}
}
if (cnt == n + 1) {
cnt = 1;
print(1, '\n');
print(2 * n, '\n');
for (int i = n; i; --i) {
st.push(a[i]);
puts("1 1 1");
while (!st.empty() and st.top() == b[cnt].v) {
puts("2 1");
++cnt, st.pop();
}
}
return true;
}
return false;
}
void init() {
opt.clear();
n = read();
for (int i = 1; i <= n; ++i) {
a[i] = read();
b[i] = {a[i], i};
pos[i] = n - i + 1;
}
sort(b + 1, b + n + 1);
}
void calculate() {
if (check()) {
return;
}
opt.push_back({1, 1, n});
for (int i = 1; i <= n; ++i) {
int t = query(pos[b[i].id]);
if (n - pos[b[i].id] - t) {
opt.push_back({3, 1, 2, n - pos[b[i].id] - t});
}
opt.push_back({2, 1});
if (n - pos[b[i].id] - t) {
opt.push_back({3, 2, 1, n - pos[b[i].id] - t});
}
update(pos[b[i].id], 1);
}
for (int i = 1; i <= n; ++i) {
update(pos[b[i].id], -1);
}
print(2, '\n');
print((int)opt.size(), '\n');
for (auto v : opt) {
for (int x : v) {
print(x, ' ');
}
putchar('\n');
}
}
void solve() {
int t = read();
while (t--) {
init();
calculate();
}
}
int main() {
solve();
return 0;
}

浙公网安备 33010602011771号