题解:AtCoder AT_abc468_b Corridor Watch

【题目来源】

AtCoder:Corridor Watch

【题目描述】

You are given integers \(M,D\) and a string \(S\) of length \(M\) consisting of G and ..

There are \(M\) cells arranged in a row from left to right, numbered \(1\) through \(M\) from the left.

Some of the cells have a guardman standing on them. Specifically, a guardman stands on cell \(i\) if \(S_i=\) G, and no one stands on cell \(i\) if \(S_i=\) ..

A cell whose distance from a cell with a guardman is at most \(D\) is watched by that guardman. That is, a cell \(x\) is watched by a guardman if there exists a cell \(i\) such that \(S_i=\) G and \(|x-i|\le D\).

Among the \(M\) cells, find the number of cells that are not watched.

给定整数 \(M, D\) 和一个长度为 \(M\)、由 G. 组成的字符串 \(S\)

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

部分格子上站着守卫。具体地,如果 \(S_i =\) G,则第 \(i\) 个格子上站着守卫;如果 \(S_i =\) .,则第 \(i\) 个格子上没有人。

与有守卫的格子的距离不超过 \(D\) 的格子会被该守卫监视。也就是说,如果存在某个格子 \(i\) 满足 \(S_i =\) G\(|x - i| \leq D\),则格子 \(x\) 被守卫监视。

\(M\) 个格子中未被监视的格子数量。

【输入】

The input is given from Standard Input in the following format:

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

【输出】

Output the answer.

【输入样例】

7 1
.G...GG

【输出样例】

1

【核心思想】

  1. 问题分析:给定 \(M\) 个格子和守卫位置,每个守卫监视距离不超过 \(D\) 的区间。求未被任何守卫监视的格子数量。这是一个差分数组 + 区间标记问题,核心在于高效处理多个区间的覆盖,避免对每个守卫的监视范围逐格遍历。

  2. 算法选择

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

    • 读入数据:读取 \(M, D\) 和字符串 \(S\)
    • 字符串索引调整\(S = ' ' + S\),使下标从 \(1\) 开始
    • 差分标记(遍历 \(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-26 17:32  团爸讲算法  阅读(0)  评论(0)    收藏  举报