二分第一个无法满足的订单编号,用差分数组 O(n) 判定前 k 份订单每天总需求是否超限,总复杂度 O((n+m)log m)。
OJ: roj
题目 ID: 3602
难度:普及+/提高-
标签:二分差分
创建: 2026-10-02 10:05
更新: 2026-10-10 11:30
形式化题目
给定
订单按输入顺序依次处理:处理第
样例:
正解
思路
朴素做法:完全按题意模拟。开一个数组 rem[] 记录每天剩余教室数,逐份处理订单,对每份订单把
观察一:失败位置具有单调性。 设 true 后 false 的单调序列:
第一个为 false 的位置 true,答案即
观察二:判定可以差分。 diff[s_j] += d_j、diff[t_j+1] -= d_j,再对 false。单次判定
两者结合:二分
下面用样例展示二分判定的核心——差分叠加。判定
| 天 |
1 | 2 | 3 | 4 |
|---|---|---|---|---|
| 供给 |
2 | 5 | 4 | 3 |
| 差分 |
+2 | +3 | 0 | −2−3=−5 |
| 前缀和 = 总需求 | 2 | 5 | 5 | 0 |
| 是否超限 | 否 | 否 | 是(5 > 4) | 否 |
看第 3 天这一列:订单 1(第 1~3 天每天 2 间)与订单 2(第 2~4 天每天 3 间)在第 3 天叠加,需求 true(见样例说明中剩余
代码
/**
* 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-10-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m; vector<ll> r,d; vector<int> s,t;
bool ok_orders(int k){ vector<ll> diff(n+1); for(int j=0;j<k;j++){ diff[s[j]-1]+=d[j]; diff[t[j]]-=d[j]; } ll cur=0; for(int i=0;i<n;i++){cur+=diff[i]; if(cur>r[i]) return false;} return true; }
int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); if(!(cin>>n>>m)) return 0; r.resize(n); for(int i=0;i<n;i++) cin>>r[i]; d.resize(m); s.resize(m); t.resize(m); for(int j=0;j<m;j++) cin>>d[j]>>s[j]>>t[j]; int lo=0,hi=m; while(lo<hi){int mid=(lo+hi+1)/2; if(ok_orders(mid)) lo=mid; else hi=mid-1;} cout << (lo==m?0:-1) << '\n'; if(lo!=m) cout << lo+1 << '\n'; return 0; }"""借教室(NOIP2012 提高组):二分第一个无法满足的订单 + 差分数组 O((n+m)log m) 判定。"""
import sys
def main() -> None:
data = sys.stdin.buffer.read().split()
it = iter(map(int, data))
n, m = next(it), next(it)
r = [next(it) for _ in range(n)] # 每天可租教室数
d = [0] * m # 订单 j 需要的教室数
s = [0] * m # 订单 j 的起始天
t = [0] * m # 订单 j 的结束天
for j in range(m):
d[j], s[j], t[j] = next(it), next(it), next(it)
def ok(k: int) -> bool:
"""前 k 份订单是否都能满足:差分累计每天总需求,与 r 比较。"""
diff = [0] * (n + 1)
for j in range(k):
diff[s[j] - 1] += d[j]
diff[t[j]] -= d[j]
cur = 0 # 当前天被占用的教室总数(前缀和)
for i in range(n):
cur += diff[i]
if cur > r[i]:
return False
return True
lo, hi = 0, m # ok(lo)=True(空集必满足),ok(hi)=False 之外即答案位置
# 二分最大的 k 使前 k 份订单全部满足;答案为 k+1,全部满足输出 0
while lo < hi:
mid = (lo + hi + 1) // 2
if ok(mid):
lo = mid
else:
hi = mid - 1
print(0 if lo == m else -1)
if lo != m:
print(lo + 1)
if __name__ == "__main__":
main()复杂度
- 时间:二分
轮,每轮差分修改 、前缀和扫描 ,共 。 - 空间:差分数组与订单数组均为
。Python 实现下 规模的列表内存约几十 MB,注意读取输入时用 sys.stdin.buffer.read()一次性读入,避免逐行 IO 拖慢速度。
一个实现细节:long long。
总结
- 朴素模拟逐区间修改,复杂度
不可接受;瓶颈是"每份订单都真实修改一天"。 - 关键观察是失败位置的单调性,把"第一个失败订单"转化为可二分的判定问题。
- 判定用差分:每份订单只改两个点,前缀和还原每日总需求,单次
。 - 总复杂度
,是处理"区间叠加 + 找第一个不合法前缀"的经典套路(二分答案 + 差分判定)。