首页 > 其他分享 >P8866 [NOIP2022] 喵了个喵

P8866 [NOIP2022] 喵了个喵

时间:2024-04-24 21:12:01浏览次数:26  
标签:空栈 int back 栈底 P8866 NOIP2022 2n id

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;
}

标签:空栈,int,back,栈底,P8866,NOIP2022,2n,id
From: https://www.cnblogs.com/FireRaku/p/18092160

相关文章

  • [NOIP2022] 种花
    题目描述小C决定在他的花园里种出\(\texttt{CCF}\)字样的图案,因此他想知道\(\textttC\)和\(\textttF\)两个字母各自有多少种种花的方案;不幸的是,花园中有一些土坑,这些位置无法种花,因此他希望你能帮助他解决这个问题。花园可以看作有\(n\timesm\)个位置的网格图,从上......
  • CSP2022 & NOIP2022
    before\(\text{inf}\)days据说今年GD参赛的人数特别多,很慌。8.01按照往年的惯例,又是一年集训时。去年没学好,只好重头开始。今年这一届的队友tql。算是基本上把深进给复习了一遍吧。8.22集训终于结束了。烦人的初赛又来了。CSP模拟套题接连不断。平均分\(70\)左......
  • NOIP2022 题解
    去年今时,我得了100+0+0+8分,太抽象了QwQ所以为什么今天才写这个东西?因为今天才做完了T2……[NOIP2022]种花简单前缀和优化DP,不谈。[NOIP2022]喵了个喵非常高级的构造题。看到\(k=2n-1/2\),我们可能会想到每一个栈内放两个即可,留一个辅助栈,即可完美过掉\(k......
  • [NOIP2022] 比赛 - 总结
    [NOIP2022]比赛0.问题转化首先需要转化为区间历史和问题。具体上来讲,就是将询问离线后,扫描线维护对于\(r\)来说,每一个\(l\)的\(\sum_{i=l}^{r}(\max_{j=l}^{i}a_j\\cdot\\max_{j=l}^{i}b_j)\)那么答案就是区间和。1.构造信息与标记接下来就是如何维护区间历史和。......
  • [NOIP2022] 喵了个喵
    补一下往年的构造题。。。\(k\)大概是\(n\)的两倍往下,这启示我们每个栈最多只放两个元素。首先考虑\(k=2n-2\)的分,容易得到一个策略:留一个空栈不放,每个栈最多放两个。如果当前卡牌存在一个栈顶/栈底和它一样,那当前牌总是可以消掉的。否则当前栈中的卡牌一定两两不同,那一定......
  • P8868 [NOIP2022] 比赛
    传送门我们容易想到预处理区间\([l,r]\)中的\(m_a\timesm_b\)。这样算出来的是一个二维的矩阵,每次的答案就是红色部分:但是这样的问题是二维的,无论如何都不是正解。考虑把列这一维压掉,也就是令\(w'_i\leftarroww_{i,i}+w_{i,i+1}+...+w_{i,r}\)。这样询问的......
  • 题解:「NOIP2022 提高组」种花
    题解:「NOIP2022提高组」种花题目大意:给定一个\(n\timesm\)的01矩阵,0表示可以种花,1表示土坑(无法种花),现在要在图上种出一个C型或F型(C,F横着的两条线的长度都可以不同,但一定是面向右边的),现在问你种C和F分别有多少种方案(除了这个形状外不能在任何地方种花),多组数据,\(T\leq5\)。......
  • P8865 [NOIP2022] 种花 题解
    前言去年多测不清空导致即便CCF放过了我的\(O(n^2m)\)的代码但依然挂成了\(0pts\)。当时看清空数组后能过CCF数据就没再管。时隔\(1\)年,重做这道题写了\(O(nm)\)的正解,终于完成了当年的心愿。\(O(n^2m)\)思路想到计算方案的话可以维护两个数组\(c1_{i,j}\)表......
  • P8868 [NOIP2022] 比赛
    主要写一写标记的推导。理论大概在关于线段树上的一些进阶操作回忆一下普通历史和。是对两个合并队列做前缀和,然后利用往后插的贡献来计算。\(ht'+add*upd\toht\)\(s*upd+ht'*len\tohs\)下文:\(x\toadda,y\toaddb\)不带历史和的点积:\((a+x)(b+y)......
  • P8867 [NOIP2022] 建造军营
    面对他。题面:求选择关键点和不会被割的边,使得任意割去一条边关键点不会有不连通的方案。考虑缩边双,然后这样边双内随便选。你考虑画出一颗树,考虑分类情况,容易发现就是三种:1.没有选。2.全部连通上\(x\)。(即一个尚未孤立的连通块)。3.有不联通到\(x\)的点。(即孤立的一......