题解:洛谷 P1503 鬼子进村

【题目来源】

洛谷:P1503 鬼子进村 - 洛谷

【题目描述】

县城里有 \(n\) 个用地道相连的房子,第 \(i\) 个只与第 \(i-1\) 和第 \(i+1\) 个相连。特别的,第 \(1\) 个房子只和第 \(2\) 个联通,第 \(n\) 个房子只和第 \(n-1\) 个联通。这时有 \(m\) 个消息依次传来:

  1. 若消息为 D x:鬼子将 \(x\) 号房子摧毁了,地道被堵上。

  2. 若消息为 R :村民们将鬼子上一个摧毁的房子修复了。

  3. 若消息为 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

【核心思想】

  1. 问题分析:给定 \(n\) 个排成一行的房子,支持三种操作:摧毁房子 D x、恢复上一个摧毁的房子 R、查询 \(x\) 号房子所在的连续未被摧毁区间的长度 Q x。这是一个区间维护 + 栈问题,关键在于用 l[i]r[i] 分别记录每个房子所在连续段的左边界和右边界,利用栈实现摧毁与恢复的顺序管理。

  2. 算法选择

    • 区间边界数组l[i] 表示房子 \(i\) 所在连续段的左端点,r[i] 表示右端点
    • 栈(Stack):记录摧毁顺序,支持 R 操作时的后进先出恢复
    • 区间分裂与合并:摧毁时分裂连续段,恢复时合并连续段
  3. 关键步骤

    • 初始化l[i] = 1, r[i] = n(所有房子连通)
    • 摧毁 D x
      • B[x] = 1x 入栈
      • 分裂左段:对 \(j\)l[x]\(x-1\)r[j] = x - 1
      • 分裂右段:对 \(j\)\(x+1\)r[x]l[j] = x + 1
    • 恢复 R
      • 取出栈顶 tB[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
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \cdot n)\),每次 DR 操作最坏遍历 \(O(n)\) 个元素更新边界
    • 空间复杂度:\(O(n)\),边界数组和栈
  5. 区间边界维护的核心思想

    • 连续段的双向标记:每个房子同时记录其所在连续段的左右边界,使得查询时 \(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
posted @ 2026-08-10 12:27  团爸讲算法  阅读(5)  评论(0)    收藏  举报