Loading

P8866 [NOIP2022] 喵了个喵 (构造)

P8866 [NOIP2022] 喵了个喵

构造模拟题,思路很简洁,但是代码不好写。

首先看到数据范围,发现 \(k\) 的数据范围很特殊,种类少一种就是部分分,所以 \(k\) 一定是关键的,先思考 \(k=2n-2\) 的情况。

\(k=2n-2\)

观察两种操作,对于即将进入的牌 \(x\),若某个栈顶或栈底有相同的 \(x\),我们都可以很快的将其消去。这启发我们

  1. 把所有栈的数量控制在 \(2\) 以内是最优的,并且所有相同类型的牌不会在栈中出现第二次。
  2. 当某类牌出现偶数次时,会被消光。

再回到数据范围,如果我们将 \(2n-2\) 种牌放到前 \(n-1\) 个栈中,由于一定有空栈,我们就可以任意消去所有类型的牌,不会无解。

\(k=2n-1\)

我们仍然想要沿用之前的思路,但是会遇到新的情况,即前 \(n-1\) 个栈都放满了,而即将放入的栈是第 \(2n-1\) 种牌 \(x\)。此时如果放入空栈,容易导致无解。

这时我们考虑什么时候不能放在空栈。发现如果放在空栈,当遇到一个目前类型在栈底的牌,并且此时它仍被栈顶挡住时,就无解了;如果两张 \(x\) 之间没有在栈底的牌,这时我们可以放入栈底。而我们可以枚举找到第一个在类型在栈底的牌 \(y\),所在的栈为 \(s\),这时再考虑什么情况它仍会被栈顶挡住。

设栈 \(s\) 的栈顶为 \(p\)

如果 \(x\)\(y\) 中的 \(p\) 数量为偶数时,把 \(x\) 放在 \(s\) 上,把所有 \(p\) 都放入空栈消掉,最后用空栈删去 \(y\)

如果 \(x\)\(y\) 中的 \(p\) 数量为奇数时,把 \(x\) 放在空栈,这是我们可以把 \(p\) 全放入 \(s\) 中把栈顶消去,最后把 \(y\) 放入 \(x\) 中,此时发现 \(s\) 栈空了,而原本的空栈多了一个 \(x\),也就是空栈变成了 \(s\)

现在思路就清晰了:

能用先前的思路就用先前的思路,不能用就分类讨论。

代码能力在这里就体现了,如果没有运用合适的数据结构就会写的很复杂。

  1. 对于可用的位置我们用队列维护,原先每个栈放两个位置进去。
  2. 对于栈,我们用双端队列维护,我们还需要每个种类的牌此时所在的位置,用数组 \(id\) 记录。
  3. 对于答案记录,边模拟边记录,数组或 vector 都可以。
  4. 由于操作会多次调用,所以考虑用函数封装,使主函数代码更简洁。

每一次枚举找到第一个在类型在栈底的牌后,我们可以直接将 \(i=r\),复杂度是 \(O(m)\)

总结:一题有思维的模拟题,需要从数据范围出发,找到启发性性质,对模拟过程有清晰思路后才开始写代码;用合适的数据结构维护数据和模拟过程。

#include <bits/stdc++.h>
typedef std::pair<int, int> pii;
typedef long long ll;
int read() {
	int x = 0, f = 1;
	char c = getchar();
	while(!isdigit(c)) {
		if(c == '-') f = -1;
		c = getchar();
	}
	while(isdigit(c)) {
		x = (x << 3) + (x << 1) + (c - '0');
		c = getchar();
	} 
	return x * f;
}
int t, n, m, k, sp;
int a[2000010], id[610];
std::deque<int> st[610];
std::vector<pii> ans;
std::queue<int> q;
void pu(int x) {
	ans.push_back({0, x});
}
void del(int x, int y) {
	ans.push_back({x, y});
}
bool normal(int a) {
	int s = id[a];
	if(!s) {
		if(q.empty()) return 0;
		s = q.front();
		q.pop();
		pu(s);
		st[s].push_back(a);
		id[a] = s; 
	}
	else {
		id[a] = 0;
		q.push(s);
		if(st[s].back() == a) {
			pu(s);
			st[s].pop_back();
		}
		else {
			pu(sp);
			del(s, sp);
			st[s].pop_front();
		}
	}
	return 1;
}
void Solve() {
	memset(id, 0, sizeof(id));
	while(!q.empty()) q.pop();
	ans.clear();
	n = read(), m = read(), k = read();
	sp = n;
	for(int i = 1; i <= m; i++) a[i] = read();
	for(int i = 1; i < n; i++) {
		q.push(i), q.push(i);
	} 
	for(int i = 1; i <= m; i++) {
		if(!normal(a[i])) {
			int r = i + 1, x = a[r], now = a[i];
			for(; r <= m && x != now && st[id[x]].back() == x; r++, x = a[r]);
			if(x == now) {
				pu(sp);
				for(int l = i + 1; l < r; l++) {
					normal(a[l]);
				}
				pu(sp);
			}
			else {
				bool flg = 0;
				int s = id[x], y = st[s].back(); 
				for(int l = i + 1; l < r; l++) {
					if(a[l] == y) {
						flg ^= 1;
					}
				}
				if(!flg) {
					pu(s);
					st[s].push_back(now);
					for(int l = i + 1; l < r; l++) {
						if(a[l] == y) pu(sp);
						else normal(a[l]);
					}
					pu(sp);
					del(s, sp);
					st[s].pop_front();
					id[x] = 0;
					id[now] = s;
				}
				else {
					pu(sp);
					st[sp].push_back(now);
					for(int l = i + 1; l < r; l++) {
						if(a[l] == y) pu(s);
						else normal(a[l]);
					}
					pu(s);
					id[x] = id[y] = 0;
					id[now] = sp;
					st[s].clear();
					q.push(sp);
					sp = s;
				}
			}
			i = r;
		}
	}
	std::cout << ans.size() << "\n";
	for(pii p : ans) {
		if(p.first == 0) std::cout << "1 " << p.second << "\n";
		else std::cout << "2 " << p.first << " " << p.second << "\n";
	}
}

int main() {
	
	t = read();
	while(t--) Solve();

	return 0;
}
posted @ 2024-04-24 21:05  Fire_Raku  阅读(71)  评论(0)    收藏  举报