题目描述
Don Giovanni\texttt{Don Giovanni}Don Giovanni 正在享用大餐,他的仆人 Leporello\texttt{Leporello}Leporello 总是在主人吃完一盘菜后,偷偷吃掉剩下的部分。但是, Leporello\texttt{Leporello}Leporello 必须随时回应主人的召唤。如果主人叫他时他嘴里还塞着食物,那就很尴尬了。
你的任务是编写一个程序,判断每次 Don Giovanni\texttt{Don Giovanni}Don Giovanni 叫 Leporello\texttt{Leporello}Leporello 时,他是否正在吃东西。
输入格式
输入的第一行是一个正整数,表示测试用例的数量。每个测试用例之间用一个空行分隔。
每个测试用例的第一行包含四个整数:
- RRR : Don Giovanni\texttt{Don Giovanni}Don Giovanni 的进食速率(单位/时间)
- rrr : Leporello\texttt{Leporello}Leporello 的进食速率(单位/时间)
- NNN : 菜肴的数量
- CCC : 召唤的次数
接下来有 NNN 行,每行包含两个整数 sss 和 ggg ,其中 s>g>0s > g > 0s>g>0 :
- sss : 菜肴初始的食物总量
- ggg : Don Giovanni\texttt{Don Giovanni}Don Giovanni 吃掉的部分
然后有 CCC 行,每行包含一个整数 ttt ,表示召唤的时间点。召唤时间按升序排列。
所有输入的数字都是不超过 200020002000 的正整数。
输出格式
对于每个测试用例,对于每个召唤,输出一行:
- 如果 Leporello\texttt{Leporello}Leporello 在召唤时间点正在吃东西,输出
full - 否则输出
clear
边界情况说明 :如果 Leporello\texttt{Leporello}Leporello 刚好在召唤时间点开始吃或结束吃,也算作 full 。例如,他在时间 202020 收到食物,在时间 404040 吃完,如果在这两个时间点被召唤,都应输出 full 。这也包括他在时间 353535 吃完一盘菜,同时在时间 353535 收到下一盘菜并被召唤的情况。
样例输入
1
10 7 5 4
60 30
150 125
55 35
70 40
42 38
2
10
19
23
样例输出
clear
clear
full
full
题目分析
问题本质
这是一个时间线模拟问题,核心在于准确追踪两个角色( Don Giovanni\texttt{Don Giovanni}Don Giovanni 和 Leporello\texttt{Leporello}Leporello )的进食时间线,并判断在特定时间点 Leporello\texttt{Leporello}Leporello 是否处于进食状态。
关键约束
- 顺序性 :两人都按照给定的菜肴顺序进食。
- 连续性 : Don Giovanni\texttt{Don Giovanni}Don Giovanni 吃完一盘菜后立即开始下一盘,而 Leporello\texttt{Leporello}Leporello 在拿到菜后立即开始吃,吃完后等待下一盘。
- 时间重叠 :如果 Leporello\texttt{Leporello}Leporello 还在吃上一盘菜时,新的一盘菜已经到来,他需要等吃完当前菜后才能开始吃新菜。
- 精确比较 :由于时间可能涉及分数,需要避免浮点数精度问题,使用分数表示时间。
解题思路
步骤 1:理解时间线
我们需要模拟两个并行的过程:
-
Don Giovanni\texttt{Don Giovanni}Don Giovanni 的时间线 :他按顺序吃每盘菜,吃每盘菜的时间为 gR\frac{g}{R}Rg 。我们可以累加这些时间得到他吃完每盘菜的时刻。
-
Leporello\texttt{Leporello}Leporello 的时间线 :他在 Don Giovanni\texttt{Don Giovanni}Don Giovanni 吃完一盘菜后立即拿到剩余食物 s−gs - gs−g ,然后以速率 rrr 开始吃,需要时间 s−gr\frac{s-g}{r}rs−g 。但这里有一个关键点:如果 Leporello\texttt{Leporello}Leporello 还在吃上一盘菜时新菜已到,他需要等吃完当前菜后才能开始吃新菜。
步骤 2:模拟 Leporello\texttt{Leporello}Leporello 的进食过程
我们可以将问题转化为区间合并问题:
- 对于每盘菜,计算 Leporello\texttt{Leporello}Leporello 可以开始吃的时间(即 Don Giovanni\texttt{Don Giovanni}Don Giovanni 吃完这盘菜的时间,但如果 Leporello\texttt{Leporello}Leporello 还在吃上一盘菜,则需要等到上一盘菜吃完)。
- 计算吃完这盘菜的时间:开始时间 + s−gr\frac{s-g}{r}rs−g 。
- 将这段时间表示为区间 [start,end][start, end][start,end] 。
- 如果新区间的开始时间等于上一个区间的结束时间,则合并这两个区间(因为 Leporello\texttt{Leporello}Leporello 连续进食)。
步骤 3:处理召唤
对于每个召唤时间 ttt ,检查 ttt 是否在任意一个进食区间内(包含端点)。如果在,则输出 full ,否则输出 clear 。
步骤 4:处理精度问题
由于时间可能不是整数,我们需要使用分数来精确表示所有时间,避免浮点精度误差。对于分数,我们需要实现:
- 分数的加法和比较运算
- 分数的简化(约分)
算法设计
数据结构
我们定义一个 Fraction 结构体来表示分数:
n: 分子d: 分母
支持的操作:
- 构造时自动约分
- 加法: ab+cd=ad+bcbd\frac{a}{b} + \frac{c}{d} = \frac{ad + bc}{bd}ba+dc=bdad+bc
- 比较:通过交叉相乘比较大小
算法流程
- 读取输入,处理多个测试用例。
- 对于每个测试用例:
- 读取 R,r,N,CR, r, N, CR,r,N,C 。
- 初始化
donTime = 0表示 Don Giovanni\texttt{Don Giovanni}Don Giovanni 的当前时间。 - 创建一个数组
events,存储每盘菜的信息:(donGiovanniFinishTime, remainingFood)。
- 计算
events:- 对于每盘菜 (s,g)(s, g)(s,g) :
- 计算 Don Giovanni\texttt{Don Giovanni}Don Giovanni 的进食时间: gR\frac{g}{R}Rg 。
- 更新
donTime = donTime + g/R。 - 记录事件:在时间
donTime,有s-g的食物留给 Leporello\texttt{Leporello}Leporello 。
- 对于每盘菜 (s,g)(s, g)(s,g) :
- 模拟 Leporello\texttt{Leporello}Leporello 的进食时间线 :
- 初始化一个空数组
intervals存储进食区间。 - 遍历每个事件
(time, food):- 计算开始时间:如果
intervals为空,则开始时间为time;否则为max(intervals.last().end, time)。 - 计算结束时间:开始时间 + foodr\frac{food}{r}rfood 。
- 如果
intervals为空或新区间的开始时间 > 上一个区间的结束时间,则添加新区间。 - 否则,合并区间:将上一个区间的结束时间更新为当前结束时间。
- 计算开始时间:如果
- 初始化一个空数组
- 处理召唤 :
- 对于每个召唤时间 ttt ,检查 ttt 是否在任意一个进食区间内(包含端点)。
- 输出
full或clear。
时间复杂度
- 对于每个测试用例:
- 计算 events : O(N)O(N)O(N)
- 模拟进食时间线 : O(N)O(N)O(N)
- 处理召唤 : O(C×M)O(C \times M)O(C×M) ,其中 MMM 是区间数量(最坏情况下 M=NM = NM=N ,但通常会更少)
- 总复杂度: O(N+C×N)O(N + C \times N)O(N+C×N) ,在题目限制下( N,C≤2000N, C \leq 2000N,C≤2000 )可以接受。
空间复杂度
- 存储 events : O(N)O(N)O(N)
- 存储 intervals : O(N)O(N)O(N)
- 总空间复杂度: O(N)O(N)O(N)
代码实现
// Don Giovanni's Last Dinner
// UVa ID: 10962
// Verdict: Accepted
// Submission Date: 2026-01-18
// UVa Run Time: 0.000s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 分数结构体
struct Fraction {
long long n, d; // 分子,分母
Fraction() : n(0), d(1) {}
Fraction(long long n, long long d = 1) : n(n), d(d) { simplify(); }
// 简化分数(约分)
void simplify() {
if (d < 0) n = -n, d = -d;
long long g = __gcd(abs(n), d);
if (g) n /= g, d /= g;
}
// 分数加法
Fraction operator+(const Fraction& other) const {
return Fraction(n * other.d + other.n * d, d * other.d);
}
// 比较运算符
bool operator<(const Fraction& other) const { return n * other.d < other.n * d; }
bool operator<=(const Fraction& other) const { return n * other.d <= other.n * d; }
bool operator==(const Fraction& other) const { return n * other.d == other.n * d; }
};
int main() {
int T;
cin >> T;
while (T--) {
int R, r, N, C;
cin >> R >> r >> N >> C;
Fraction donTime(0, 1);
vector<pair<Fraction, int>> events;
// 读取每盘菜的信息,计算 Don Giovanni 吃完每盘菜的时间
for (int i = 0; i < N; i++) {
int s, g;
cin >> s >> g;
donTime = donTime + Fraction(g, R);
events.push_back({donTime, s - g});
}
// 模拟 Leporello 的进食过程,合并连续进食区间
vector<pair<Fraction, Fraction>> intervals;
for (auto& evt : events) {
// 计算开始吃这盘菜的时间
Fraction startTime = intervals.empty() ? evt.first : intervals.back().second;
if (startTime < evt.first) startTime = evt.first;
// 计算吃完这盘菜的时间
Fraction endTime = startTime + Fraction(evt.second, r);
// 判断是否需要合并区间
if (intervals.empty() || intervals.back().second < startTime)
intervals.push_back({startTime, endTime});
else
intervals.back().second = endTime;
}
// 处理每个召唤
for (int i = 0; i < C; i++) {
int t;
cin >> t;
Fraction callTime(t, 1);
bool full = false;
// 检查召唤时间是否在任意一个进食区间内
for (auto& seg : intervals)
if (seg.first <= callTime && callTime <= seg.second) {
full = true;
break;
}
cout << (full ? "full" : "clear") << '\n';
}
// 测试用例之间输出空行
if (T) cout << '\n';
}
return 0;
}
总结
本题的关键在于:
- 理解时间线的并行性 : Don Giovanni\texttt{Don Giovanni}Don Giovanni 和 Leporello\texttt{Leporello}Leporello 的进食过程需要同时追踪。
- 正确处理等待情况 :当 Leporello\texttt{Leporello}Leporello 还在吃上一盘菜时,新菜需要等待。
- 使用分数避免精度问题 :这是解决本题的核心技巧,确保边界情况的正确性。
- 区间合并 :将连续的进食时间合并为单个区间,简化判断逻辑。
通过将问题分解为 Don Giovanni\texttt{Don Giovanni}Don Giovanni 的时间线计算、 Leporello\texttt{Leporello}Leporello 的进食区间合并和召唤时间判断三个步骤,我们可以清晰地解决这个看似复杂的时间模拟问题。

3282

被折叠的 条评论
为什么被折叠?



