2025.03.26 CW 模拟赛 B. 栈法师

B. 栈法师

题目描述

传说中存在着一扇神秘的魔法大门,它通向着无尽的魔法宝藏。但是这扇大门只对那些能够熟练运用栈法杖的魔法师敞开。

大门的密码是一个序列 \(a[1...n]\),为了解锁大门,魔法师需要将 \(a\) 中的数字输出到另一个序列 \(b\),并保证 \(b\) 是非降序的。魔法师携带了 \(k\) 根栈法杖作为解锁这扇神秘大门的工具。开始时,这些栈法杖都是空的,他可以使用三种栈法杖操作:

  1. \((1, i)\):从序列 \(a\) 的末尾取出一个数字(栈的 pop 操作),并将它储存到第 \(i\) 根栈法杖中(栈的 push 操作)。

  2. \((2, i)\):从第 \(i\) 根栈法杖中取出最后一个储存的数字(栈的 pop 操作),并将其添加到输出序列 \(b\) 的末尾(栈的 push 操作)。

  3. \((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;
}
posted @ 2025-03-26 21:08  Steven1013  阅读(36)  评论(0)    收藏  举报