题解:洛谷 AT_abc438_c 1D puyopuyo

【题目来源】

洛谷:AT_abc438_c [ABC438C] 1D puyopuyo - 洛谷

【题目描述】

给定一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

你可以执行以下操作零次或多次(任意顺序,任意次数):

· 选择一个整数 \(k\) 满足 \(1 \leq k \leq |A|-3 且 A_k=A_{k+1}=A_{k+2}=A_{k+3}\),然后从 \(A\) 中删除 \(A_k,A_{k+1},A_{k+2},A_{k+3}\)。(更准确地说,将 \(A\) 替换为 \((A_1,A_2,\ldots,A_{k-1},A_{k+4},A_{k+5},\ldots,A_N)\)。)

这里,\(|A|\) 表示整数序列 \(A\) 的长度。

求重复进行操作后,最终 \(|A|\) 可能的最小值。

【输入】

输入以以下格式从标准输入给出:

\(N A_1 A_2 \ldots A_N\)

【输出】

输出重复进行操作后,最终 \(|A|\) 可能的最小值。

【输入样例】

10
1 1 1 4 4 4 4 1 2 3

【输出样例】

2

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的整数序列 \(A\),每次可选择一段连续且颜色相同的 \(4\) 个元素进行删除,删除后序列自动拼接。求经过任意次操作后,序列 \(A\) 可能的最小长度。这是一个栈模拟消除问题,关键在于处理"消除后新形成的连续段可能继续触发消除"的连锁反应。

  2. 算法选择

    • 栈(Stack):用栈维护当前序列中各颜色段的连续信息,栈顶始终代表当前序列末尾的颜色段
    • 贪心策略:每次遇到与栈顶相同颜色的元素就合并,一旦某颜色段连续个数达到 \(4\) 立即消除,保证局部最优即全局最优
  3. 关键步骤

    • 初始化:读取 \(N\) 和序列 \(A\),创建空栈 \(st\),栈中每个元素为 \(\{color, cnt\}\)(颜色,连续个数)
    • 逐元素入栈处理(遍历 \(i\)\(1\)\(N\),当前颜色为 \(x\)):
      • 若栈为空或 \(x \neq st.top().first\):将 \(\{x, 1\}\) 压入栈,表示新颜色段开始
      • \(x = st.top().first\):栈顶计数 \(cnt\)\(1\),即 \(st.top().second++\)
    • 消除判断
      • 若栈顶计数达到 \(4\),即 \(st.top().second = 4\),执行 \(st.pop()\) 消除该段
      • 消除后栈顶变为新的末尾段,后续元素可能与该段继续合并,形成连锁消除
    • 统计答案
      • 遍历栈中所有元素,累加各段的 \(cnt\)\(ans = \sum_{j=1}^{|st|} st_j.cnt\)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个元素最多入栈一次、出栈一次
    • 空间复杂度:\(O(N)\),最坏情况下栈中存储所有元素(无任何消除)
  5. 栈模拟消除的核心思想

    • 后进先出匹配:栈顶始终代表当前序列的末尾段,新元素只可能与末尾段合并,天然符合"消除后拼接"的操作语义
    • 即时消除策略:连续个数达到 \(4\) 立即弹出,确保消除后栈顶变为前一段,后续元素可继续与其合并,自动处理连锁反应
    • 贪心最优性:越早消除越能为后续创造更多消除机会,栈的即时消除保证每一步都是局部最优,最终得到全局最小长度
    • 状态压缩:将连续相同颜色压缩为 \(\{color, cnt\}\) 二元组,避免存储每个单独元素,降低空间开销
    • 适用于"相邻相同元素消除/合并"类问题,尤其是消除后会影响前驱状态的连锁场景

【算法标签】

普及- #栈

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 200005;
typedef pair<int, int> PII;  // 定义pair类型,first存储颜色,second存储连续个数

int n;  // 彩珠总数
stack<PII> st;  // 栈,存储{颜色, 连续个数}

int main()
{
    // 输入彩珠总数
    cin >> n;

    // 处理每个彩珠
    for (int i = 1; i <= n; i++)
    {
        int x;  // 当前彩珠的颜色
        cin >> x;

        // 如果栈为空 或 当前颜色与栈顶颜色不同
        if (st.empty() || x != st.top().first)
        {
            // 新颜色入栈,连续个数为1
            st.push({x, 1});
        }
        // 如果当前颜色与栈顶颜色相同
        else if (x == st.top().first)
        {
            // 栈顶颜色的连续个数加1
            st.top().second++;

            // 如果连续个数达到4,消除这4个连续的彩珠
            if (st.top().second == 4)
            {
                st.pop();
            }
        }
    }

    // 计算消除后剩余的彩珠总数
    int ans = 0;
    while (!st.empty())
    {
        // 累加每个颜色段剩余的彩珠数
        ans += st.top().second;
        st.pop();
    }

    // 输出剩余彩珠数
    cout << ans << endl;

    return 0;
}

【运行结果】

10
1 1 1 4 4 4 4 1 2 3
2
posted @ 2026-08-27 11:04  团爸讲算法  阅读(6)  评论(0)    收藏  举报