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

P8866 [NOIP2022] 喵了个喵

时间:2022-12-04 14:12:29浏览次数:78  
标签:相消 int 元素 栈顶 st P8866 NOIP2022 2n

P8866 NOIP2022 喵了个喵 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

本题解中我们将图案为 \(x\) 的卡牌看做数字 \(x\),将本题对于卡牌的操作看做对数字的操作。

观察到数据范围,\(k \in \{2n - 2, 2n - 1\}\),那么做法肯定跟 \(k\) 强相关。很难不联想 2018 年的旅行。但是那个 \(m = n - 1\) 给了 60 分,这个呢?

\(k = 2n - 2\)

首先发现 \(op\) 的限制其实是假的:我们一定恰好进行了 \(m\) 次操作 \(1\),最多进行了 \(0.5m\) 次操作 \(2\),所以 \(op\) 一定天然满足 \(m \le op \le 1.5m\)。

观察 \(k\) 和 \(n\) 的关系,发现是近似二倍,而且 \(k = 2(n - 1)\),也就是我们如果把每两种数字考虑到一个栈,这样分配将恰好剩下一个空栈。

考虑将数字 \(2i - 1\) 和 \(2i\) 分配到第 \(i\) 个栈中,第 \(n\) 个栈就可以作为空辅助栈了。稍作思考可以给出以下的解法:

遇到一个数字 \(x\),我们查看这个数字所分配到栈 \(s\) 的情况:

  • 如果 \(s\) 为空,直接入栈;
  • 如果 \(s\) 有 \(1\) 个元素,和 \(x\) 相同,将 \(x\) 入栈 \(s\) 并将这两个 \(x\) 消除;
  • 如果 \(s\) 有 \(1\) 个元素,和 \(x\) 不同,将 \(x\) 入栈 \(s\) ;
  • 如果 \(s\) 有 \(2\) 个元素,栈顶和 \(x\) 相同,将 \(x\) 入栈 \(s\) 并将这两个 \(x\) 消除;
  • 如果 \(s\) 有 \(2\) 个元素,栈顶和 \(x\) 不同,那么栈底一定和 \(x\) 相同。将 \(x\) 入辅助栈 \(n\),对栈 \(n\) 和栈 \(s\) 栈底相消。

容易看出,我们第偶数次遇到 \(x\) 的时候,总能立刻将它和上一个 \(x\) 抵消,所以一定能报告出解。

另外,可以注意到本题的操作只对栈顶和栈底有效,所以如果栈中某时元素很多就会造成大量的滞留,非常不优。这也和本做法中栈的大小始终不超过 \(2\) 有关系(达到 \(3\) 之后可以马上通过相消降为 \(2\))。

这样 15 分就到手了。是 60 分的四分之一。

\(k = 2n - 1\)

一种比较显然的想法是,对于数字 \(x \le 2n - 2\) 我们暂时仍然考虑上面的处理方式,然后考虑如何处理 \(x = 2n - 1\)。发现遇到 \(x = 2n-1\) 的时候局面的结构比较参差不定,不好想(后来发现好像也不是不可以,读者可以自己试试?)。

我选择了另一种想法,那就是抛开栈 \(i\) 和数字 \(2i\),\(2i - 1\) 的绑定关系,采用先来后到的思想,按照 \(k = 2n-2\) 的策略尽可能走下去。换句话说,当一个没出现在局面上的数即将来临时,临时给它绑定一个没有绑定满两个元素的栈(注意只有 \(n-1\) 个栈可用);当一个数在局面上消失时,立刻让它和栈失去绑定关系,然后接着使用 \(k = 2n-2\) 的策略。这时当走不下去的时候,一定是局面上的 \(n-1\) 个栈全部被填满各两个元素,每个元素恰出现一次,且接下来要填的数是另一个数 \(P\),此时局面结构就是固定的了。

提一嘴,我感觉在剩下的 85 分中,应该是有部分数据可以按照上述策略成功走完整个游戏的,毕竟随机数据下,出现走不下去的概率不是特别高(当然也不难构造)。所以也不失为一个骗分好办法。不过这题有多测,所以也有可能会卡。

直接把 \(P\) 放到辅助栈显然是错的:假如 \(P\) 后面跟了一个被压在栈底的数字,那么这两个数字可能很难再抵消,很不优。

我们考虑离线:也就是说,我们开始预知这个数后面都是什么数。依据后面数的情况,我们考虑 \(P\) 放在哪里。

考虑紧跟在 \(P\) 后方最长的一串数,满足这些数都在栈顶。如果不考虑当前 \(P\) 的去向,那么直接将这些数放到对应的栈,和栈顶相消即可,如果再来就仍然放置原来的栈顶。比如 \(5\) 是某个栈的栈顶,如果 \(P\) 后面来了个 \(5\) 就让它和 \(5\) 相消,如果再来一个 \(5\) 就放在原来的栈顶,如果再来就继续相消。因此我们发现,这些数还是相当自由的,我暂且称之为自由相消。

因此发现,如果 \(P\) 后面第一个不在栈顶上的数是 \(P\),那么我们就把当前的 \(P\) 放入辅助栈。第二个 \(P\) 来时,将它也放入辅助栈,栈顶相消即可。中间的数自由相消就行。这种情况比较简单。

现在讨论 \(P\) 后面第一个不在栈顶上的数是一个栈底 \(X\) 的情况。我们记其对应栈为 \(S\)。此时未来的数列是 \((P, \cdots, X)\),其中 \(\cdots\) 都是某个栈的栈顶。我们讨论 \(S\) 当前的栈顶 \(Y\)。

  • 如果未来数列的 \(P\) 和 \(X\) 之间,\(Y\) 出现了奇数次:把 \(P\) 放入辅助栈,接下来一直自由相消,直到将处理 \(X\) 为止。由于 \(Y\) 的数量是奇数,加上一开始的一个 \(Y\),此时 \(Y\) 一定被消除光,现在 \(S\) 的栈顶变成 \(X\),将 \(X\) 放入 \(S\) 栈顶相消,\(S\) 变成了空栈。
  • 如果 \(Y\) 出现了偶数次:把 \(P\) 放入 \(S\),未来所有不为 \(Y\) 的栈顶自由相消,对于 \(Y\),我们将其全部放入辅助栈相消,直到将处理 \(X\) 为止。由于 \(Y\) 的数量是偶数,这些 \(Y\) 一定都被消除光,辅助栈仍为空,我们将 \(X\) 放入辅助栈,和 \(S\) 栈底相消,辅助栈仍为空。

经过上述操作后,局面总会保持:

  • 存在一个空栈;
  • 除了某个数以外,其它数都在局面最多出现一次;
  • 所有栈最多只有两个元素。

直接让新的空栈作为辅助栈即可。

如何证明这种做法一定可以报告出解?其实很简单,只要不是在 \((P, \cdots, X)\) 的特殊处理过程期间,我们的操作总能保持让每个数最多只在局面出现一次;本题只能两两相消,每种数字数量均为偶数,因此最终每个数必定不会在局面中出现一次,也就是必定不会出现。

具体实现临时绑定和松绑时,用一个队列维护没有绑定满两个元素的栈的编号,当然,辅助栈不能入队。对于未绑满两个元素的栈,如果它绑定了一个元素,让它在队列中出现一次,否则如果没绑定元素,出现两次即可。当出现一个元素不在局面上,需要绑定栈,但队列为空时,证明我们要开始特殊操作了。

时间复杂度 \(\Theta(m)\)。

/*
 * @Author: crab-in-the-northeast 
 * @Date: 2022-12-04 01:03:11 
 * @Last Modified by: crab-in-the-northeast
 * @Last Modified time: 2022-12-04 13:35:14
 */
#include <bits/stdc++.h>
inline int read() {
    int x = 0;
    bool f = true;
    char ch = getchar();
    for (; !isdigit(ch); ch = getchar())
        if (ch == '-')
            f = false;
    for (; isdigit(ch); ch = getchar())
        x = (x << 1) + (x << 3) + ch - '0';
    return f ? x : (~(x - 1));
}

const int maxn = 305;
const int maxm = (int)2e6 + 5;

int a[maxm];
std :: deque <int> st[maxn];
int id[maxn * 2]; // 维护所在栈编号,局面未出现则为 0
bool top[maxn * 2]; // 维护是否在栈顶

typedef std :: pair <int, int> pii;
std :: vector <pii> ans;

inline void pu(int s) {
    ans.emplace_back(s, 0);
}

inline void de(int s, int t) {
    ans.emplace_back(s, t);
}

int spt; // 辅助栈编号
std :: queue <int> stq; // 维护空栈

inline bool simple(int x) { // 尝试简单相消
    int s = id[x];
    if (!s) {
        if (stq.empty())
            return false;
        id[x] = s = stq.front();
        stq.pop();
    }
    int z = st[s].empty() ? 0 : st[s].front();
    if (!st[s].empty() && x == st[s].back()) {
        pu(s);
        id[x] = 0;
        stq.push(s);
        top[z] = true;
        top[x] = false;
        // 注意上面两个语句顺序不能颠倒,因为 z 和 x 相等时我们期望其 top 为 false
        st[s].pop_back();
    }
    else if (st[s].size() < 2) {
        pu(s);
        st[s].push_back(x);
        top[z] = false;    
        top[x] = true;
        // 同样不能颠倒
    } else {
        pu(spt), de(spt, s);
        id[z] = 0;
        top[z] = false;
        st[s].pop_front();
        stq.push(s);
    }
    return true;
}

int main() {
    for (int T = read(); T; --T) {
        int n = read(), m = read(); read();
        for (int i = 1; i <= m; ++i)
            a[i] = read();
        std :: memset(id, 0, sizeof(id));
        ans.clear();
        spt = n;

        while (!stq.empty())
            stq.pop();
        for (int i = 1; i < n; ++i) {
            stq.push(i);
            stq.push(i);
        }
        
        for (int i = 1; i <= m; ++i) if (!simple(a[i])) {
            int p = a[i];
            int r = i + 1;
            for (; r <= m && top[a[r]]; ++r);
            // 此时 r 是 i 后第一个不在栈顶上的数
            int x = a[r];
            if (x == p) {
                pu(spt);
                for (int j = i + 1; j < r; ++j)
                    simple(a[j]);
                pu(spt);
            } else {
                int s = id[x], y = st[s].back();
                bool evn = true; // 中间栈顶中,y 的数量是否为偶数
                for (int j = i + 1; j < r; ++j)
                    if (a[j] == y)
                        evn = !evn;
                if (evn) {
                    pu(s);
                    st[s].push_back(p);
                    for (int j = i + 1; j < r; ++j) {
                        if (a[j] == y)
                            pu(spt);
                        else
                            simple(a[j]);
                    }
                    pu(spt);
                    de(spt, s);
                    st[s].pop_front();
                    // st[s] 从栈底到栈顶,原先为 x, y,现在为 y, p
                    // 依此更新 id 和 top
                    id[x] = 0;
                    id[p] = s;
                    top[y] = false;
                    top[p] = true;
                } else {
                    pu(spt);
                    st[spt].push_back(p);
                    for (int j = i + 1; j < r; ++j) {
                        if (a[j] == y)
                            pu(s); // 注意这里不要直接 simple(a[j])
                        // 原因是 s 即将变成 spt,所以暂时不能让 s 弹入 stq
                        // 特判让 simple 函数此时不把 s 不弹入 stq 也不行
                        // 假如 3 1 2 1 1 1 ....
                        // 如果直接不弹入 stq,会造成后面的一串 1 1 1 无法正确弹入 s
                        // 最后暴力扫队列把 s 删除复杂度也不对
                        // 所以正确的做法就是不用 simple 函数
                        else
                            simple(a[j]);
                    }
                    pu(s);
                    st[s].clear();
                    // 原先辅助栈 spt 此时存在一个元素 p
                    // s 原先栈底到栈顶为 x,y, 现在为空
                    // s 作为新的 spt
                    // 依此更新 id,top 和 stq
                    id[x] = id[y] = 0;
                    id[p] = spt;
                    top[y] = false;
                    top[p] = true;
                    stq.push(spt);
                    spt = s;
                }
            }

            i = r; // 注意循环会自带 ++i,下一次循环 i = r + 1
        }

        printf("%d\n", (int)ans.size());
        for (pii p : ans) {
            if (p.second)
                printf("2 %d %d\n", p.first, p.second);
            else
                printf("1 %d\n", p.first);
        }
    }
}

标签:相消,int,元素,栈顶,st,P8866,NOIP2022,2n
From: https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p8866.html

相关文章

  • NOIP2022游寄
    真的是游了,寄了Day0预感要爆炸,背了背模板(虽然没用上)Day1感觉不怎么好,买了瓶咖啡,到考场的时候发现掉了,555提前进入考场静坐密码是biu#2019miss和???(记不到了)提前......
  • 【洛谷P8866】喵了个喵
    题目题目链接:https://www.luogu.com.cn/problem/P8866小E喜欢上了一款叫做《喵了个喵》的游戏。这个游戏有一个牌堆和\(n\)个可以从栈底删除元素的栈,任务是要通过游......
  • NOIP2022T3
    NOIP2022T3建造军营题解[NOIP2022]建造军营题目描述A国与B国正在激烈交战中,A国打算在自己的国土上建造一些军营。A国的国土由\(n\)座城市组成,\(m\)条双向道路......
  • NOIP2022 题解
    A.种花有的人把名字写进题面,想“不朽”。签到题。枚举c和f的最左边那一列的位置,然后做一个类似前缀和的东西。B.喵了个喵压轴题。首先\(k=2n-2\)有一个非常好......
  • NOIP2022T1题解
    [NOIP2022]种花(民间数据)题目描述小C决定在他的花园里种出\(\texttt{CCF}\)字样的图案,因此他想知道\(\textttC\)和\(\textttF\)两个字母各自有多少种种花的方......
  • NFLSPC #5 & NOIP2022 游记
    Day?接到百度之星决赛电话的时候,区运会还没有推迟,我也不知道这周末会补考。这样NOIP,区运会,百度之星,还有补考,成功撞在了周末短短两天内。我为区运会推掉了百度之星的邀......
  • P8866 [NOIP2022] 喵了个喵
    \(\mathcalLink\)当\(k=2n-2\):保证任意时刻每种元素只出现一次,并保留一个空栈,让其他栈大小不超过\(2\)即可。当\(k=2n-1\):延续上面的做法,对于多出来的第\(2n-1\)......
  • NOIP2022 游记
    怕不是真要NOIP退役了……Day-10果断停课,回归机房。(但whk已经在上三角函数了……血亏,以后再补吧)补了补之前的题目。晚上模拟赛爆零了!!好耶(埋伏笔)Day-9~Day-5......
  • NOIP2022 VP 游寄
    考前给大家说一下我糟糕的模拟赛成绩:\(\operatorname{rk}29\)(总人数\(32\))感觉NOIP2022无望了。(最后再说一句,我的CSP/S太菜了,才\(165\)分,无缘NOIP正式名额,只......
  • NOIP2022
    NOIP2022总结考前早上主要看了点随机化和部分分写法什么的感觉有点脱离了大纲,图论那块一直不是很好也没怎么复习有点紧张,尽管不是现场赛,但是还是有点怕分数太低QA......