题解:洛谷 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

【核心思想】

  1. 问题分析:高桥在时间 \(0\) 打开 chokutter,青木在 \(A_i\) 时刻经过时若 chokutter 开着则高桥立即关闭,关闭后 \(100\) 时间单位自动重新打开。求工作时间段 \([0, T]\) 内 chokutter 总共开放的时长。这是一个时间区间模拟问题,核心在于追踪 chokutter 的开关状态变化,累加各开放区间的长度。

  2. 算法选择

    • 事件驱动模拟:按时间顺序处理每个青木经过事件,根据当前状态决定是否关闭 chokutter,并计算开放时长
  3. 关键步骤

    • 读入数据\(N\)\(T\),以及 \(N\) 个事件时间 \(A[1..N]\)
    • 初始化open = trueopen_time = 0ans = 0
    • 遍历事件\(i\)\(1\)\(N\)):
      • 开放状态open == true):
        • 累加开放时长:\(ans \leftarrow ans + (A_i - open\_time)\)
        • 关闭 chokutter:open = falseclose_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 = falseclose_time = A_i,`open_time = A_i + 100$
    • 处理最后一段:若 \(T \geq open\_time\)\(ans \leftarrow ans + (T - open\_time)\)
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),遍历事件一次
    • 空间复杂度:\(O(N)\),存储事件时间
  5. 事件驱动模拟的核心思想

    • 状态机的精确建模: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
posted @ 2026-08-27 11:06  团爸讲算法  阅读(5)  评论(0)    收藏  举报