当前位置: 首页 > article >正文

【洛谷 P9025】 [CCC2021 S3] Lunch Concert 题解

题目: 洛谷 P9025

分析: 为了解决这个问题,我们需要找到一个整数位置 c 来举办音乐会,使得所有人移动到能听到音乐会的位置的时间总和最小。每个人移动后的位置应该在其听力范围内,并且尽可能靠近自己的初始位置以减少移动距离。

方法思路

  1. 问题分析:

    • 每个人的听力范围是 [c - D_i, c + D_i],其中 D_i 是第 i 个人的听力距离。
    • 如果初始位置 P_i 在听力范围内,移动距离为0。
    • 如果 P_i 在听力范围左侧,需要移动到 c - D_i,移动距离为 (c - D_i) - P_i
    • 如果 P_i 在听力范围右侧,需要移动到 c + D_i,移动距离为 P_i - (c + D_i)
  2. 关键观察:

    • 总移动时间是关于 c 的分段线性函数,具有最小值点。
    • 最小值点出现在总斜率由负变正的区间内,此时总移动时间最小。
  3. 算法选择:

    • 收集所有关键点(听力范围的左右边界),并排序。
    • 计算每个区间的总斜率,找到总斜率由负变正的区间,该区间内的所有 c 对应的总移动时间相同且最小。

解决代码

#include <bits/stdc++.h>  // 包含所有标准库头文件
using namespace std;
using ll = long long;  // 长整型别名,处理大整数(题目提示答案可能超过2^32)// 事件结构体:记录听力范围边界点及对总斜率的影响
struct Event {ll x;       // 事件点位置(听力范围的左/右边界)ll delta;   // 该事件点引起的总斜率变化量(+W[i])Event(ll x, ll delta) : x(x), delta(delta) {}  // 构造函数// 按事件点位置排序(用于后续处理区间)bool operator<(const Event& other) const {return x < other.x;}
};int main() {ios::sync_with_stdio(false);  // 加速输入输出cin.tie(nullptr);int N;cin >> N;  // 读取人数vector<ll> P(N), W(N), D(N);  // 存储每个人的初始位置、速度、听力距离vector<Event> events;          // 存储所有事件点(听力范围边界)ll sum_W = 0;                  // 所有人的速度之和(用于计算初始斜率)// 读取每个人的信息,并生成事件点for (int i = 0; i < N; ++i) {cin >> P[i] >> W[i] >> D[i];sum_W += W[i];  // 累加速度总和// 每个人的听力范围是 [c-D[i], c+D[i]](c是音乐会位置)// 但我们需要分析当c变化时,每个人的移动时间如何变化// 关键观察:当c跨过 P[i]-D[i] 或 P[i]+D[i] 时,移动时间的斜率会变化ll A = P[i] - D[i];  // 听力范围左边界(当c < A时,此人需要右移)ll B = P[i] + D[i];  // 听力范围右边界(当c > B时,此人需要左移)// 事件点:当c经过A或B时,总斜率会变化// 每个事件点对应一个“斜率变化”:遇到左边界A时,斜率增加W[i];遇到右边界B时,斜率也增加W[i]events.emplace_back(A, W[i]);  // 左边界事件events.emplace_back(B, W[i]);  // 右边界事件}// 按事件点位置排序(后续按顺序处理区间)sort(events.begin(), events.end());// 初始总斜率K:总移动时间关于c的导数// 当c非常小时(远小于所有A和B),所有人都需要右移,移动时间总和为 sum(W[i]*(P[i] - c - D[i]))// 对c求导得总斜率为 -sum(W[i])(因为每个项的导数是 -W[i])ll K = -sum_W;ll x_prev = -1e18;  // 上一个事件点的位置(初始为负无穷)// 遍历所有事件点,寻找总斜率由负变正的转折点for (size_t i = 0; i < events.size(); ++i) {ll x = events[i].x;  // 当前事件点位置// 当总斜率K >= 0时,说明当前区间的左端点x_prev到当前x之间的c值,总移动时间最小// 因为总移动时间是分段线性函数,当斜率由负变正时,最小值出现在斜率为0的区间if (K >= 0) {ll c = x_prev;  // 取区间左端点(或区间内任意整数,结果相同)ll total = 0;   // 总移动时间// 计算每个人的移动时间for (int j = 0; j < N; ++j) {ll A_j = P[j] - D[j];  // 第j个人的听力左边界ll B_j = P[j] + D[j];  // 第j个人的听力右边界// c在听力范围左侧:需要移动到A_j(即c-D[j]),移动距离为 (A_j - P[j]) = (c - D[j] - P[j])?不,原逻辑可能需要重新核对// 注意:原问题中,当音乐会位置是c时,此人的听力范围是 [c - D[j], c + D[j]]// 所以正确的判断是:// 如果 P[j] < c - D[j]:此人需要右移到c - D[j],移动距离 = (c - D[j] - P[j])// 如果 P[j] > c + D[j]:此人需要左移到c + D[j],移动距离 = (P[j] - (c + D[j]))// 如果在范围内:移动距离0// 但原代码中的计算逻辑可能存在笔误,正确的计算应为:if (c < (c - D[j])) {  // 这显然不可能,原代码可能将A和B定义为相对于P[j]的边界?// 重新理解原事件点的定义:// 原代码中的A = P[i] - D[i],B = P[i] + D[i],这实际是当c的听力范围覆盖P[i]时的c的边界// 例如,当c >= A(即c >= P[i]-D[i])时,P[i]可能进入听力范围左侧;当c <= B(即c <= P[i]+D[i])时,P[i]可能进入听力范围右侧// 可能原代码的事件点定义是:当c超过A时,该人的移动时间斜率变化;当c超过B时,斜率再次变化// 因此,正确的移动时间计算应为:if (c < A_j) {  // 音乐会位置c在A_j左侧(即c < P[j]-D[j]):此人的听力范围左边界是c-D[j],但P[j]在更左,所以需要右移到c-D[j]total += W[j] * (c - D[j] - P[j]);  // 移动距离 = (c-D[j] - P[j]),时间=距离*速度W[j](注意:W是秒每米,所以时间=距离*W)} else if (c > B_j) {  // 音乐会位置c在B_j右侧(即c > P[j]+D[j]):此人的听力范围右边界是c+D[j],但P[j]在更右,需要左移到c+D[j]total += W[j] * (P[j] - (c + D[j]));  // 移动距离 = (P[j] - (c+D[j])),时间=距离*W[j]}// 否则(c在[A_j, B_j]之间):P[j]在听力范围内,移动距离0,时间0}cout << total << endl;  // 输出最小总时间return 0;}K += events[i].delta;  // 处理当前事件点,更新总斜率(遇到事件点后,斜率增加delta)x_prev = x;  // 记录上一个事件点位置}// 遍历完所有事件点后,检查是否还有K>=0的情况(理论上应该已经处理)if (K >= 0) {ll c = x_prev;ll total = 0;for (int j = 0; j < N; ++j) {ll A_j = P[j] - D[j];ll B_j = P[j] + D[j];if (c < A_j) {total += W[j] * (c - D[j] - P[j]);} else if (c > B_j) {total += W[j] * (P[j] - (c + D[j]));}}cout << total << endl;}return 0;
}

代码解释

  1. 输入处理:

    • 读取输入数据,包括人数 N 和每个人的位置 P_i、速度 W_i、听力距离 D_i
  2. 事件点收集:

    • 收集每个听力范围的左右边界作为事件点,并记录每个事件点对总斜率的贡献。
  3. 排序和斜率计算:

    • 对事件点排序,计算初始总斜率 K(初始为所有速度的负值和)。
  4. 寻找最小时间区间:

    • 遍历事件点,维护当前总斜率 K,找到第一个总斜率非负的区间。
  5. 计算最小时间:

    • 在找到的区间内取左端点 c,计算总移动时间并输出。

该方法通过分析总移动时间的分段线性特性,高效地找到最小值点,确保了算法的时间复杂度和正确性。

http://www.lryc.cn/news/2383743.html

相关文章:

  • Python 包管理工具核心指令uvx解析
  • 苍穹外卖05 Redis常用命令在Java中操作Redis_Spring Data Redis使用方式店铺营业状态设置
  • AI工程师系列——面向copilot编程
  • 【竖排繁体识别】如何将竖排繁体图片文字识别转横排繁体,转横排简体导出文本文档,基于WPF和腾讯OCR的实现方案
  • 梳理Spring Boot中三种异常处理
  • NFS服务器实验
  • ffmpeg 转换视频格式
  • Java进阶之新特性
  • Python基础学习-Day32
  • 离线服务器算法部署环境配置
  • AIGC工具平台-卡通图片2D转绘3D
  • docker 启动一个python环境的项目dockerfile版本
  • Java虚拟机 -方法调用
  • 基于matlabcd7.x的无网格近似方法
  • JMeter JDBC请求Query Type实测(金仓数据库版)
  • 【内部教程】ISOLAR-AB配置以太网栈|超详细实战版
  • 哈希表和容器中添加元素的方法
  • Nginx 核心功能
  • String.join()-高效字符串拼接
  • 【Canvas与图标】圆角方块蓝星CSS图标
  • 系统性能分析基本概念(5) : 何时开始性能分析
  • Python实现Web请求与响应
  • 机器学习 day05
  • CentOS Stream安装MinIO教程
  • C#新建打开文件对话框
  • 汇川PLC通过开疆智能Profinet转ModbusTCP网关读取西门子PLC数据案例
  • 零基础入门:MinerU 和 PyTorch、CUDA的关系
  • 借助IEDA ,Git版本管理工具快速入门
  • 三维空间,毫秒即达:RTMP|RTSP播放器在Unity中的落地实现
  • 【计算机网络】HTTP/1.0,HTTP/1.1,HTTP/2,HTTP/3汇总讲解,清晰表格整理面试重点对比