P8866 [NOIP2022] 喵了个喵 Solution

更好的阅读体验戳此进入

题面

存在 n 个栈,给定 m 张卡牌,每张卡牌有图案 ai,给定 k,有 ai[1,k]。保证每种图案的卡牌数均为偶数。初始所有栈为空。

你可以进行两种操作:

输入保证有解,求一个合法操作方案。

Solution

这道题实际上是可以通过每个部分分来慢慢推出方案的。

首先第一档部分分,对于 k=2n2,不难想到,我们将一个栈空出来作为备用栈,并保证其它栈里永远最多只有两个卡牌,也就是一个在栈顶一个在栈底,实现上可以把所有可以放卡牌的栈顶和栈底存下来并动态维护,注意要先用栈底再插栈顶。然后顺序遍历所有卡牌。如果当前所有栈中不存在这个颜色,那么找个栈内卡牌数不足 2 的丢进去。如果存在的话,考虑如果其在栈顶,那就把这个卡牌放在对应的栈顶来消除,然后重新将这个位置置为可以放置卡牌。如果在栈底那么将这张卡牌放在备用栈里,然后用操作二将这一对删除即可。

然后考虑后面的 k=2n1 的部分分。我们还是按照刚才的思路,保留一个备用栈,然后老样子处理卡牌。显然处理一段时间之后就会出现一个状态,即已经有 2n2 个颜色的卡牌被放到了栈里,然后此时又多了个新的栈中不存在的颜色的卡牌。

这时不难想到有如下方案:我们先将这张卡牌搁置,令其颜色为 a,然后继续向后遍历,如果再碰到了颜色为 a 的直接结束,这里还要注意如果循环结束之后 a 的两个位置的答案没有被更新,那么需要额外更新一下,即把两个 a 都塞进备用栈里消除一下。然后考虑过程中碰到的其它颜色的卡牌,显然第一次遇到的时候其一定是在前面的栈中存在的,因为颜色一共就有 2n1 个。如果碰到在栈顶的颜色,那么直接将其消除,并且标记这个该颜色的位置为这个位置,如果本次遍历过程中再次碰到这个颜色还要再次把这个颜色放到这个位置上。如果碰到在栈底的颜色,那么这时候考虑当前其对应的栈顶是否已经在本次遍历过程中被删除。我们令这个栈底颜色为 b,栈顶颜色为 c

如果 c 被删除的话,那么我们将 a 放在备用栈里,然后将新的 b 放到原来的 b 栈,将其消除,则此时备用栈的位置会多一个栈顶的空位,b 栈会变空,则我们这时直接将 b 栈作为新的备用栈即可让状态又变成原来的样子。

如果 c 依然存在,那么先将 a 放在 b 栈里,然后再将新的 b 放到备用栈里,这样可以将 b 栈和备用栈栈底的卡牌对应消除掉,此时则 b 栈又变回两个元素,备用栈又为空。

然后这个时候我们可能会发现一点问题,比如如果有一个实际的结果:(2,1,3,1,1,1,1)(这里记左侧为栈底,右侧为栈顶),但是按照我们的写法实际是变成了 (2,1,1,1,1,1,3),表面上这些是不同的,但是结果上其都为 (2,1,3)。对于剩下的 1 为奇数个的,写一下会发现也是等效的。

思路看起来还是比较简单容易想的,但是这东西实现起来就能感受到恶心了,这里为了方便理解,大概说明一下代码中的部分关键变量名的作用。

对于每个 pair 前者表示栈的索引,后者为栈顶或栈底,1 表示栈底,0 表示栈顶,ans 存的是答案,mp 指的是某个颜色对应的在栈中的位置,stk 存储的是对应位置中存的是什么颜色,mark 是指对应颜色被固定的位置(在遇到 a 之后遍历的时候使用),unfix 存储的是所有空着的可以使用的栈的位置。

写在后面

建议先做 T3 T4 再做这题

这个应该算是我写过最恶心的题之一了,调代码已经人调麻了。如果是在考场上我绝对部分分一拿就跑路。最开始有个思路然后写了一百多行写完了,调试的过程中发现假掉了。。。然后是参考的 @sssmzy 的思路,上面说的也是这个做法,然后做完就开始阴间调试的过程。最后本地对拍全过然后交上去就 WA,各种找样例各种手动模拟,不知道调了多久才找到问题:备用栈切换的时候 unfix 中可能有剩余的新栈中的空位,需要删除。

然后改来改去的代码可能变得很难看,敬请谅解,并且我的实现也可能不够精细,或许会有更简便的方法和更简便的实现,这里只是提供一个思路。还有个问题就是这个 erase 理论上最坏可能是 O(n) 的,理论上似乎最坏情况下 O(S) 次,O(nS) 似乎理论上能被卡,但是这也是理论上,实际上完全卡不满,基本上是无法被卡掉的,并且本身 O(nS) 也超的不多,再加上 O2 那基本上就是稳稳过。

Code

UPD

update-2022_12_01 初稿