题解:洛谷 AT_abc443_c Chokutter Addiction
【题目来源】
洛谷:AT_abc443_c [ABC443C] Chokutter Addiction - 洛谷(luogu.com.cn)
【题目描述】
AtCoder 公司在时间 \(0\) 开始工作,在时间 \(T\) 结束工作。时间 \(t\) 与时间 \(t+1\) 之间的间隔为 \(1\) 秒。
高桥君在 AtCoder 公司的工作时间内按照以下规则查看 SNS chokutter:
- 他在工作开始的同时打开 chokutter。
- 如果青木君经过高桥君办公桌后面时 chokutter 处于打开状态,他会立即关闭 chokutter。
- 如果高桥君在 \(t\) 时间关闭chokutter,他总是在 \(t+100\) 时间打开它。
从工作开始到结束,青木君从高桥君办公桌后面经过了 \(N\) 次,其中 \(i\) 次是在 \(A_i\) 时间。
从工作开始到结束,高桥君一共看了多少秒钟的 chokutter?
高桥君打开 chokutter 的瞬间,青木君不会从办公桌后面经过。
【输入】
输入内容由标准输入法提供,格式如下:
\(N\) \(T\)
\(A_1\) \(A_2\) \(\dots\) \(A_N\)
【输出】
输出答案。
【输入样例】
5 700
100 150 300 350 700
【输出样例】
500
【核心思想】
-
问题分析:高桥在时间 \(0\) 打开 chokutter,青木在 \(A_i\) 时刻经过时若 chokutter 开着则高桥立即关闭,关闭后 \(100\) 时间单位自动重新打开。求工作时间段 \([0, T]\) 内 chokutter 总共开放的时长。这是一个时间区间模拟问题,核心在于追踪 chokutter 的开关状态变化,累加各开放区间的长度。
-
算法选择:
- 事件驱动模拟:按时间顺序处理每个青木经过事件,根据当前状态决定是否关闭 chokutter,并计算开放时长
-
关键步骤:
- 读入数据:\(N\)、\(T\),以及 \(N\) 个事件时间 \(A[1..N]\)
- 初始化:
open = true,open_time = 0,ans = 0 - 遍历事件(\(i\) 从 \(1\) 到 \(N\)):
- 开放状态(
open == true):- 累加开放时长:\(ans \leftarrow ans + (A_i - open\_time)\)
- 关闭 chokutter:
open = false,close_time = A_i,`open_time = A_i + 100$
- 关闭状态且未到开放时间(\(A_i < open\_time\)):忽略该事件
- 关闭状态且已到开放时间(\(A_i \geq open\_time\)):
- 累加开放时长:\(ans \leftarrow ans + (A_i - open\_time)\)
- 再次关闭:
open = false,close_time = A_i,`open_time = A_i + 100$
- 开放状态(
- 处理最后一段:若 \(T \geq open\_time\),\(ans \leftarrow ans + (T - open\_time)\)
- 输出结果:\(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),遍历事件一次
- 空间复杂度:\(O(N)\),存储事件时间
-
事件驱动模拟的核心思想:
- 状态机的精确建模:chokutter 只有两种状态——开放和关闭。开放时遇到青木事件则关闭并设置 \(100\) 时间后自动开放;关闭时遇到事件若未到开放时间则忽略,若已到开放时间则先开放再立即关闭(因为事件发生时 chokutter 已开)
- 开放区间的累加策略:每次状态从开放转为关闭时,累加"本次开放时间 \(\to\) 关闭时间"的区间长度。最后一段从最后一次开放时间到 \(T\) 单独处理
- 事件时间的排序利用:输入事件已按时间排序,按顺序处理即可,无需额外排序
- 边界条件处理:
open_time记录的是"下次可开放时间",而非"当前开放时间"。在关闭状态下到达open_time时,chokutter 自动打开,若此时恰好有青木事件,则立即关闭,代码中通过ans += A_i - open_time正确计算了这瞬间的开放时长(为 \(0\) 或正数) - 适用于时间区间模拟、状态机、事件驱动类问题
【算法标签】
普及- #模拟
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 300005; // 数组最大容量
int n, t, ans, open_time, close_time; // n: 事件数量,t: 总时间,ans: 累计开放时间,open_time: 下次可开放时间,close_time: 上次关闭时间
int a[N]; // 存储事件发生时间
bool open; // 当前状态:true表示开放,false表示关闭
int main()
{
cin >> n >> t; // 读入事件数量和总时间
// 读入事件发生时间
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
open = true; // 初始状态为开放
for (int i = 1; i <= n; i++)
{
if (open == true) // 当前处于开放状态
{
// 累加从上次开放时间到当前事件时间的开放时长
ans += a[i] - open_time;
open = false; // 标记为关闭
close_time = a[i]; // 记录关闭时间
open_time = a[i] + 100; // 计算下次可开放时间(100时间单位后)
}
else if (open == false && a[i] > close_time && a[i] < open_time) // 关闭期间有事件但未到下次开放时间
{
continue; // 忽略该事件,状态不变
}
else if (open == false && a[i] >= open_time) // 关闭期间有事件且已到下次开放时间
{
// 累加从下次开放时间到当前事件时间的开放时长
ans += a[i] - open_time;
open = false; // 仍标记为关闭(因为当前事件导致关闭)
close_time = a[i]; // 记录新的关闭时间
open_time = a[i] + 100; // 计算新的下次开放时间
}
}
// 处理最后一段开放时间(从最后一次开放时间到总时间t)
if (t >= open_time)
{
ans += t - open_time;
}
// 输出总开放时间
cout << ans << endl;
return 0;
}
【运行结果】
5 700
100 150 300 350 700
500
浙公网安备 33010602011771号