1 条题解
-
0
题解
这是一道贪心 + 预处理前后最近起点 + 二分/公式计算的经典题,数据范围 ,要求 或 算法。
一、题目翻译与核心规则
题意
体育场有 条跑道,第 条跑道长度为 (跑完需要 秒)。 有 个运动员,第 个运动员从跑道 的起点出发。
所有运动员训练 秒,规则如下:
- 运动员在跑道 起点,跑完这条跑道耗时 秒(算完成 1 圈);
- 跑完后瞬间移动,可以选择:
- 回到当前跑道 起点;
- 移动到相邻跑道 或 起点;
- 总时间不超过 秒,求所有运动员能跑完的最大完整圈数。
关键结论(贪心核心)
要跑最多圈,最优策略一定是:
- 从起点出发,单向移动到某一条跑道 (只往左/只往右);
- 到达 后,一直在 反复跑圈,不再移动。 原因:移动不耗时,反复跑同一条跑道效率最高。
二、核心公式推导
设:
- 运动员从起点 出发,走到目标跑道 ;
- 移动路径的总耗时:(只跑沿途跑道各一次,耗时为路径和);
- 剩余时间:;
- 目标跑道 每圈耗时 ,可跑圈数:$\left\lfloor \dfrac{T - \mathrm{cost}(s,x)}{a_x} \right\rfloor$;
- 总圈数 = 沿途跑道数(移动时跑的) + 反复跑圈数。
三、标程核心思路
1. 预处理前缀和
作用: 计算任意区间 的耗时和。
2. 预处理左右最近起点(最关键)
- :位置 左边最近的运动员起点(包含自己);
- :位置 右边最近的运动员起点(包含自己);
这样每个位置 ,只需要计算两个起点:
- 左边最近起点 到 ;
- 右边最近起点 到 。
3. 遍历所有位置计算最大圈数
对每个跑道 :
- 计算从 走到 的耗时;
- 若耗时 ,计算总圈数;
- 同理计算 走到 ;
- 维护全局最大值。
四、标程函数详解
1.
go(f, t):计算从起点 到目标 的移动耗时ll go(ll f,ll t){ if(t>f) return s[t-1]-s[f-1]; // 向右走:跑 f ~ t-1 各一次 else return s[f]-s[t]; // 向左走:跑 t+1 ~ f 各一次 }- 向右 :耗时 =
- 向左 :耗时 =
2.
cnt(f, t):计算从 出发到 的总圈数ll cnt(ll f,ll t){ if(t==f) return T/a[t]; // 不移动,直接跑 if(t>f) return (t-f)+(T-go(f,t))/a[t]; // 向右:步数+剩余圈数 else return (f-t)+(T-go(f,t))/a[t]; // 向左:步数+剩余圈数 }- :移动时跑的圈数;
- :剩余时间反复跑的圈数。
五、完整算法流程
- 输入:,数组 ,起点数组 ;
- 前缀和:;
- 预处理 数组:从左到右,记录最近起点;
- 预处理 数组:从右到左,记录最近起点;
- 遍历每个位置 :
- 用 计算到 的圈数;
- 用 计算到 的圈数;
- 更新答案最大值;
- 输出答案。
六、样例演算(第一个样例)
输入: 起点:
预处理
计算位置 5
- 最近起点 ;
- ;
- 剩余时间 ;
- 反复跑 ,圈数 ;
- 总圈数 → 最大值。
输出:。
七、复杂度分析
- 前缀和:
- 预处理 数组:
- 遍历计算: 总复杂度:,完美通过 数据。
八、标程代码注释版
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll MAXN = 3e5 + 10; ll n, m, T; ll a[MAXN], b[MAXN], s[MAXN]; // s:前缀和 ll l[MAXN], r[MAXN]; // l[i]:左最近起点,r[i]:右最近起点 ll ans = -1e18; // 计算从 f 到 t 的总圈数 ll cnt(ll f, ll t) { if (t == f) return T / a[t]; if (t > f) return (t - f) + (T - (s[t-1] - s[f-1])) / a[t]; else return (f - t) + (T - (s[f] - s[t])) / a[t]; } // 计算从 f 到 t 的移动耗时 ll go(ll f, ll t) { if (t > f) return s[t-1] - s[f-1]; else return s[f] - s[t]; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n >> m >> T; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= m; i++) cin >> b[i]; // 前缀和 for (int i = 1; i <= n; i++) s[i] = s[i-1] + a[i]; // 初始化起点标记 for (int i = 1; i <= m; i++) l[b[i]] = r[b[i]] = b[i]; // 预处理左最近起点 for (int i = 1; i <= n; i++) if (!l[i]) l[i] = l[i-1]; // 预处理右最近起点 for (int i = n; i >= 1; i--) if (!r[i]) r[i] = r[i+1]; // 遍历所有位置,计算最大圈数 for (int i = 1; i <= n; i++) { if (l[i] && go(l[i], i) <= T) ans = max(ans, cnt(l[i], i)); if (r[i] && go(r[i], i) <= T) ans = max(ans, cnt(r[i], i)); } cout << ans << endl; return 0; }
总结
- 贪心策略:先移动到最优跑道,再反复刷圈;
- 优化核心:用 数组跳过无效起点, 查询最近起点;
- 公式:总圈数 = 移动步数 + 剩余时间//单圈耗时;
- 复杂度:,通过全部测试点。
- 1
信息
- ID
- 6385
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者