题解:洛谷 P1503 鬼子进村
【题目来源】
【题目描述】
县城里有 \(n\) 个用地道相连的房子,第 \(i\) 个只与第 \(i-1\) 和第 \(i+1\) 个相连。特别的,第 \(1\) 个房子只和第 \(2\) 个联通,第 \(n\) 个房子只和第 \(n-1\) 个联通。这时有 \(m\) 个消息依次传来:
-
若消息为
D x:鬼子将 \(x\) 号房子摧毁了,地道被堵上。 -
若消息为
R:村民们将鬼子上一个摧毁的房子修复了。 -
若消息为
Q x:有一名士兵被围堵在 \(x\) 号房子中。
现定义能够到达如下:若存在房子 \(i,j(1\leq i\leq j\leq n)\),使得对于任意的 \(k(i\leq k\leq j)\) 都满足房子 \(k\) 未被摧毁,则称房子 \(i\) 与房子 \(j\) 互相能够到达。
李云龙收到信息很紧张,他想知道每一个被围堵的士兵能够到达的房子有几个。
【输入】
第一行两个整数 \(n,m\)。
接下来 \(m\) 行,有如题目所说的三种信息共 \(m\) 条。
【输出】
对于每一个被围堵的士兵,输出该士兵能够到达的房子数。
【输入样例】
7 9
D 3
D 6
D 5
Q 4
Q 5
R
Q 4
R
Q 4
【输出样例】
1
0
2
4
【核心思想】
-
问题分析:给定 \(n\) 个排成一行的房子,支持三种操作:摧毁房子
D x、恢复上一个摧毁的房子R、查询 \(x\) 号房子所在的连续未被摧毁区间的长度Q x。这是一个区间维护 + 栈问题,关键在于用l[i]和r[i]分别记录每个房子所在连续段的左边界和右边界,利用栈实现摧毁与恢复的顺序管理。 -
算法选择:
- 区间边界数组:
l[i]表示房子 \(i\) 所在连续段的左端点,r[i]表示右端点 - 栈(Stack):记录摧毁顺序,支持
R操作时的后进先出恢复 - 区间分裂与合并:摧毁时分裂连续段,恢复时合并连续段
- 区间边界数组:
-
关键步骤:
- 初始化:
l[i] = 1, r[i] = n(所有房子连通) - 摧毁
D x:B[x] = 1,x入栈- 分裂左段:对 \(j\) 从
l[x]到 \(x-1\),r[j] = x - 1 - 分裂右段:对 \(j\) 从 \(x+1\) 到
r[x],l[j] = x + 1
- 恢复
R:- 取出栈顶
t,B[t] = 0 - 合并左段:对 \(j\) 从
l[t]到 \(t-1\),r[j] = r[t] - 合并右段:对 \(j\) 从 \(t+1\) 到
r[t],l[j] = l[t]
- 取出栈顶
- 查询
Q x:- 若
B[x] = 1,输出 \(0\) - 否则输出
r[x] - l[x] + 1
- 若
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(m \cdot n)\),每次
D或R操作最坏遍历 \(O(n)\) 个元素更新边界 - 空间复杂度:\(O(n)\),边界数组和栈
- 时间复杂度:\(O(m \cdot n)\),每次
-
区间边界维护的核心思想:
- 连续段的双向标记:每个房子同时记录其所在连续段的左右边界,使得查询时 \(O(1)\) 得到区间长度
- 摧毁的分裂效应:摧毁房子 \(x\) 后,原连续段 \([l, r]\) 分裂为 \([l, x-1]\) 和 \([x+1, r]\),需更新两段内所有房子的边界
- 恢复的合并效应:恢复房子 \(t\) 后,左右两段重新合并为 \([l[t], r[t]]\),利用恢复前保存的边界信息一次性更新
- 栈的顺序管理:
R操作总是恢复最后摧毁的房子,栈的后进先出特性恰好匹配这一需求 - 适用于"一维序列的区间分裂与合并 + 顺序恢复"问题,核心在于用边界数组维护连续段,栈管理操作顺序
【算法标签】
普及+ #栈
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int n, m, B[N], l[N], r[N];
stack<int> stk;
char c;
int t;
int main()
{
cin >> n >> m; // 输入房间数量n和操作次数m
for (int i = 1; i <= n; i++)
l[i] = 1, r[i] = n; // 初始化每个房间的左边界和右边界
for (int i = 1; i <= m; i++) // 处理每个操作
{
cin >> c; // 输入操作类型
if (c == 'D') // 破坏操作
{
cin >> t; // 输入要破坏的房间编号
stk.push(t); // 入栈,用于恢复
B[t] = 1; // 标记为被破坏
// 更新左边房间的右边界
for (int j = l[t]; j < t; j++)
r[j] = t - 1;
// 更新右边房间的左边界
for (int j = t + 1; j <= r[t]; j++)
l[j] = t + 1;
}
if (c == 'R') // 恢复操作
{
t = stk.top(); // 取出最后被破坏的房间
stk.pop(); // 出栈
B[t] = 0; // 取消破坏标记
// 恢复左边房间的右边界
for (int j = l[t]; j < t; j++)
r[j] = r[t];
// 恢复右边房间的左边界
for (int j = t + 1; j <= r[t]; j++)
l[j] = l[t];
}
if (c == 'Q') // 查询操作
{
cin >> t; // 输入要查询的房间编号
if (B[t]) // 如果房间被破坏
cout << 0 << endl; // 输出0
else
cout << r[t] - l[t] + 1 << endl; // 输出连续段的长度
}
}
return 0;
}
【运行结果】
7 9
D 3
D 6
D 5
Q 4
1
Q 5
0
R
Q 4
2
R
Q 4
4
浙公网安备 33010602011771号