题解: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
【核心思想】
-
问题分析:给定 \(M\) 个格子和守卫位置,每个守卫监视距离不超过 \(D\) 的区间。求未被任何守卫监视的格子数量。这是一个差分数组 + 区间标记问题,核心在于高效处理多个区间的覆盖,避免对每个守卫的监视范围逐格遍历。
-
算法选择:
- 差分数组(Difference Array):将每个守卫的监视区间 \([l, r]\) 转化为差分数组的两个单点更新,最后通过前缀和还原每个格子的监视状态
-
关键步骤:
- 读入数据:读取 \(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\)
- 若 $S_i = $
- 前缀和还原(\(i\) 从 \(1\) 到 \(M\)):
- \(diff[i] \leftarrow diff[i] + diff[i - 1]\)
- 若 \(diff[i] = 0\):\(ans \leftarrow ans + 1\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(M)\),遍历字符串一次标记,一次前缀和还原
- 空间复杂度:\(O(M)\),差分数组
-
差分数组与区间覆盖的核心思想:
- 区间操作降维:差分数组将区间 \([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
浙公网安备 33010602011771号