题解:洛谷 AT_abc468_b Corridor Watch

【题目来源】

洛谷:AT_abc468_b [ABC468B] Corridor Watch

【题目描述】

给定整数 \(M\)\(D\) 以及长度为 \(M\) 的字符串 \(S\)\(S\) 由字符 G. 组成。

\(M\) 个格子从左到右排成一排,编号为 \(1\)\(M\)

有些格子上站着守卫。具体来说,如果 \(S_i\)G,则第 \(i\) 个格子上有守卫;如果 \(S_i\).,则第 \(i\) 个格子无人。

距离某个有守卫的格子的距离不超过 \(D\) 的格子会被该守卫看守。也就是说,如果存在某个 \(i\) 满足 $S_i = $ G\(|x-i| \le D\),那么格子 \(x\) 会被守卫看守。

请找出在这 \(M\) 个格子中,没有被任何守卫看守的格子的数量。

【输入】

输入从标准输入读取,格式如下:

\(M\) \(D\) \(S\)

【输出】

输出答案。

【输入样例】

7 1
.G...GG

【输出样例】

1

【核心思想】

  1. 问题分析:给定 \(M\) 个格子、守卫位置字符串 \(S\) 和监视半径 \(D\),每个守卫 \(i\)($S_i = $ G)监视区间 \([i-D, i+D]\)。求未被任何守卫监视的格子数量。这是一个差分数组 + 区间覆盖问题,核心在于高效处理多个守卫的监视区间叠加。

  2. 算法选择

    • 差分数组(Difference Array):将每个守卫的监视区间转化为差分数组的两个单点更新,最后通过前缀和还原每个格子的被监视次数
  3. 关键步骤

    • 读入数据:读取 \(M\)\(D\) 和字符串 \(S\)
    • 差分标记(遍历 \(i\)\(1\)\(M\)):
      • 若 $S_i = $ G
        • \(l = \max(1, i - D)\)\(r = \min(M, i + D)\)
        • \(diff[l] \leftarrow diff[l] + 1\)
        • \(diff[r + 1] \leftarrow diff[r + 1] - 1\)
    • 前缀和还原\(i\)\(1\)\(M\)):
      • \(diff[i] \leftarrow diff[i] + diff[i - 1]\)
      • \(diff[i] = 0\)\(ans \leftarrow ans + 1\)
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(M)\),遍历字符串一次标记,一次前缀和还原
    • 空间复杂度:\(O(M)\),差分数组
  5. 差分数组与区间覆盖的核心思想

    • 区间操作降维:差分数组将区间 \([l, r]\) 的加 \(1\) 操作转化为 \(diff[l] += 1\)\(diff[r+1] -= 1\) 两个 \(O(1)\) 操作,避免 \(O(D)\) 的逐格遍历
    • 前缀和还原:通过一次前缀和遍历,将差分数组还原为每个位置的实际覆盖次数。\(diff[i] > 0\) 表示被监视,\(diff[i] = 0\) 表示未被监视
    • 边界处理\(l\)\(r\) 需要限制在 \([1, M]\) 范围内,防止数组越界
    • 多守卫叠加:差分数组天然支持多个区间的叠加,每个守卫的贡献在前缀和后自动累加
    • 适用于区间覆盖、批量标记类基础问题

【算法标签】

入门 #差分

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long    // 将int定义为long long,避免数据范围溢出
const int N = 105;       // 定义数组最大容量为105
int m, d, ans;           // m为格子总数,d为守卫监视半径,ans记录未被监视的格子数量
string s;                // 存储M个格子的状态,'G'表示有守卫,'.'表示空
int diff[N];             // 差分数组,用于高效标记每个守卫的监视区间

signed main()            // 使用signed main配合#define int long long
{
    cin >> m >> d;       // 读入格子总数M和监视半径D
    cin >> s;            // 读入格子状态字符串
    s = " " + s;         // 在字符串前添加一个空格,使下标从1开始,方便处理
    for (int i = 1; i <= m; i++)  // 遍历每个格子
    {
        if (s[i] == 'G')  // 如果第i个格子有守卫
        {
            int l = max(0LL, i - d);   // 计算该守卫监视区间的左端点(不能小于0)
            int r = min(m, i + d);     // 计算该守卫监视区间的右端点(不能超过m)
            diff[l] += 1;              // 在差分数组的左端点位置加1,标记区间开始
            diff[r + 1] -= 1;          // 在差分数组右端点的下一个位置减1,标记区间结束
        }
    }
    for (int i = 1; i <= m; i++)  // 遍历每个格子,通过前缀和还原实际监视次数
    {
        diff[i] += diff[i - 1];   // 累加差分数组,得到第i个格子被多少个守卫监视
        if (diff[i] == 0)         // 如果第i个格子未被任何守卫监视
            ans++;                // 未被监视的格子数量加1
    }
    cout << ans << endl;          // 输出未被监视的格子总数
    return 0;
}

【运行结果】

7 1
.G...GG
1
posted @ 2026-07-28 20:19  团爸讲算法  阅读(14)  评论(0)    收藏  举报