二分最长运行时间,用各设备累计能量缺口与充电宝总供能构造单调可行性判定。
OJ: luogu
题目 ID: P3743
难度:普及/提高-
标签:二分答案贪心浮点数python
日期: 2026-07-16 17:49
目录
题意
有
求所有设备能够共同运行的最长时间;如果可以永远运行,输出 -1。
思路
先判断能否无限运行
记所有设备的总耗能率为:
如果 -1。
如果
固定时间后,只统计能量缺口
假设要让所有设备至少运行
所以截至
充电宝在
充电宝可以连续、抢占式地在设备之间切换,能量也可以提前存入设备。可以把需要补给设备
按截止时间从早到晚充电。若这种策略第一次在时刻
为什么只检查终点 就够了
仅写出
随着
若某个时刻
也就是
二分右边界
初始总能量记为:
运行
因为当前讨论的是
因此可以把
Python 二分 60 次,C++ 二分 80 次。数据范围下右边界最多约为
正确性说明
- 若
,充电宝的总功率覆盖总耗能率,通过连续分时供能可以无限运行。 - 若
,总能量以正的净速率减少,答案有限,并且不超过 。 - 对固定时刻
,第 台设备必须从充电宝获得的最少能量恰为 ,所有设备的最小总需求就是 。 单调不减,所以终点满足 时,每个更早时刻也满足对应前缀约束;反之若终点需求超过供能,则显然不可能运行到 。 - 因而
possible(t)与真实可行性等价,且具有单调性。二分保留最大可行时间所在区间,最终得到所需精度的答案。
brute.py 按设备自然耗尽时刻 Fraction 在每段线性函数上精确求交点。它是独立的对拍参考解,不是更适合提交的朴素算法,因此正文不重复嵌入。
Python 知识
一次读取全部整数
题目只有整数 token,换行位置不影响解析,因此可以使用:
data = list(map(int, sys.stdin.buffer.read().split()))这会一次读取全部输入并转换为整数列表,适合
用切片和 zip 重组设备
去掉前两个数 n, p 后,输入按 a_1,b_1,a_2,b_2,... 交错排列。代码使用:
devices = list(zip(data[2::2], data[3::2]))第一个切片取得所有耗能率,第二个切片取得所有初始能量,zip 再把同一设备的两个值配对。
生成器只产生需要聚合的值
判定函数先按需产生每台设备的缺口,再只汇总正缺口:
lacks = (use * seconds - stored for use, stored in devices)
needed = sum(lack for lack in lacks if lack > 0.0)生成器不会额外创建一个长度为 sum 遍历一次。
Fraction 只用于验证
参考程序用 fractions.Fraction 精确表示 float 更合适。
C++ 到 Python 对照
- C++ 的两个全局数组对应 Python 的
(use, stored)元组列表。 - C++ 在判定中用普通循环并在超出供能时提前返回;Python 用生成器和
sum表达同一个缺口总和。 - C++ 的
long long保存整数总量;Python 整数自动扩展,不会发生固定宽度溢出。 - C++ 的
fixed << setprecision(10)对应 Python 的f"{left:.10f}"。
模仿清单
- 纯整数输入且不关心换行时,用
sys.stdin.buffer.read().split()。 - 交错数组可以用两个步长切片配合
zip重组。 - “先变换、再筛选、最后求和”可以用一次性生成器交给
sum。 - 浮点正解可以用
Fraction分段公式作为小数据独立参考,避免两个程序共享同一种误差。
相关 Python 笔记:
/home/rainboy/mycode/hugo-blog/content/program_language/python/generator_expression.md:生成器的惰性计算、一次性消费以及与sum的配合。/home/rainboy/mycode/hugo-blog/content/program_language/python/oj_input_output_cheatsheet.md:大量纯整数 token 的读取与固定小数位输出。/home/rainboy/mycode/hugo-blog/content/program_language/python/math_tools.md:浮点比较和fractions.Fraction精确有理数。
代码
C++17 正解
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-19 09:46
* update_at: 2026-07-19 09:59
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n;
long long charger_power;
long long use_rate[MAXN];
long long stored_energy[MAXN];
bool possible(double seconds) {
double available = charger_power * seconds;
double needed = 0.0;
for (int i = 1; i <= n; i++) {
double lack = use_rate[i] * seconds - stored_energy[i];
if (lack > 0) {
needed += lack;
if (needed > available) return false;
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> charger_power;
long long total_use = 0;
long long total_stored = 0;
for (int i = 1; i <= n; i++) {
cin >> use_rate[i] >> stored_energy[i];
total_use += use_rate[i];
total_stored += stored_energy[i];
}
if (total_use <= charger_power) {
cout << -1 << '\n';
return 0;
}
double left = 0.0;
double right = (double)total_stored / (total_use - charger_power);
for (int iteration = 1; iteration <= 80; iteration++) {
double middle = (left + right) / 2;
if (possible(middle)) {
left = middle;
} else {
right = middle;
}
}
cout << fixed << setprecision(10) << left << '\n';
return 0;
}Python 正解
import sys
data = list(map(int, sys.stdin.buffer.read().split()))
n, power = data[:2]
devices = list(zip(data[2::2], data[3::2]))
total_use = sum(use for use, _ in devices)
if total_use <= power:
print(-1)
else:
def possible(seconds):
lacks = (use * seconds - stored for use, stored in devices)
needed = sum(lack for lack in lacks if lack > 0.0)
return needed <= power * seconds
left = 0.0
right = sum(stored for _, stored in devices) / (total_use - power)
for _ in range(60):
middle = (left + right) / 2
if possible(middle):
left = middle
else:
right = middle
print(f"{left:.10f}")复杂度
设二分迭代次数为
- 读取数据和计算总量需要
时间。 - 每次可行性判定扫描全部设备,需要
时间。 - 总时间复杂度为
;Python 中 ,C++ 中 ,都可以视为 。 - 保存
台设备,空间复杂度为 。
C++ 判定在总缺口已经超过可用能量时提前退出;Python 的生成器不保存缺口列表,因此额外临时空间为
总结
连续切换不需要真的模拟。固定一个目标时间
更关键的是,