题解:洛谷 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
【核心思想】
-
问题分析:给定长度为 \(N\) 的整数序列 \(A\),每次可选择一段连续且颜色相同的 \(4\) 个元素进行删除,删除后序列自动拼接。求经过任意次操作后,序列 \(A\) 可能的最小长度。这是一个栈模拟消除问题,关键在于处理"消除后新形成的连续段可能继续触发消除"的连锁反应。
-
算法选择:
- 栈(Stack):用栈维护当前序列中各颜色段的连续信息,栈顶始终代表当前序列末尾的颜色段
- 贪心策略:每次遇到与栈顶相同颜色的元素就合并,一旦某颜色段连续个数达到 \(4\) 立即消除,保证局部最优即全局最优
-
关键步骤:
- 初始化:读取 \(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每个元素最多入栈一次、出栈一次
- 空间复杂度:\(O(N)\),最坏情况下栈中存储所有元素(无任何消除)
-
栈模拟消除的核心思想:
- 后进先出匹配:栈顶始终代表当前序列的末尾段,新元素只可能与末尾段合并,天然符合"消除后拼接"的操作语义
- 即时消除策略:连续个数达到 \(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
浙公网安备 33010602011771号