阈值 n 固定时顺着日志模拟能 AC 的题数,它随 n 单调不增,因此二分出所有满足 count(n)=k 的整数区间。
OJ: luogu
题目 ID: P4343
难度:普及+/提高
标签:二分模拟思维
日期: 2026-06-20 13:41
题意
自动刷题机每天会按照日志修改代码行数:
- 正数表示新增若干行
- 负数表示删除若干行,删过头就变成
0
若某一秒结束时代码长度达到某个未知阈值 n,
就会立刻 AC 一题,并把代码清空重新开始。
现在已知整天一共 AC 了 k 题,
要求求出这个阈值 n 可能的最小值和最大值。
思路
先看一个可以直接验证想法的朴素解:
cpp
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int MAXN = 205;
int l, target_k;
i64 a[MAXN];
int count_ac(i64 need) {
i64 cur = 0;
int cnt = 0;
for (int i = 1; i <= l; i++) {
cur += a[i];
if (cur < 0) cur = 0;
if (cur >= need) {
cnt++;
cur = 0;
}
}
return cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> l >> target_k;
i64 sum_pos = 0;
for (int i = 1; i <= l; i++) {
cin >> a[i];
if (a[i] > 0) sum_pos += a[i];
}
i64 first = -1;
i64 last = -1;
for (i64 n = 1; n <= sum_pos; n++) {
if (count_ac(n) == target_k) {
if (first == -1) first = n;
last = n;
}
}
if (first == -1) cout << -1 << '\n';
else cout << first << ' ' << last << '\n';
return 0;
}固定阈值 n 后,这题就只剩一件事:
- 按日志模拟整天最后能 AC 几题
设这个函数为 count(n)。
模拟规则非常直接:
- 当前代码长度加上这秒的变化量
- 若小于
0,就归零 - 若达到
n,就 AC 一题,并把当前代码长度清零
关键观察在于:
n越大,达到阈值越难- 所以
count(n)随n增大单调不增
于是所有满足:
count(n) = k
的整数 n 一定构成一个连续区间。
因此只需要二分两次:
- 找最小的
n,使count(n) <= k - 找最大的
n,使count(n) >= k
如果左端点处 count(n) 都不是 k,
说明根本不存在这样的阈值,输出 -1。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
const int MAXN = 100000 + 5;
int l;
int target_k;
i64 a[MAXN];
int count_ac(i64 need) {
i64 cur = 0;
int cnt = 0;
for (int i = 1; i <= l; i++) {
cur += a[i];
if (cur < 0) cur = 0;
if (cur >= need) {
cnt++;
cur = 0;
}
}
return cnt;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> l >> target_k;
i64 max_prefix = 0;
i64 cur = 0;
for (int i = 1; i <= l; i++) {
cin >> a[i];
cur += a[i];
if (cur < 0) cur = 0;
if (cur > max_prefix) max_prefix = cur;
}
// 找最小的 n,使 count_ac(n) <= k。
i64 left = 1, right = max_prefix + 1;
while (left < right) {
i64 mid = (left + right) >> 1;
if (count_ac(mid) <= target_k) right = mid;
else left = mid + 1;
}
i64 min_n = left;
if (count_ac(min_n) != target_k) {
cout << -1 << '\n';
return 0;
}
// 找最大的 n,使 count_ac(n) >= k。
left = 1, right = max_prefix + 1;
while (left < right) {
i64 mid = (left + right + 1) >> 1;
if (count_ac(mid) >= target_k) left = mid;
else right = mid - 1;
}
i64 max_n = left;
cout << min_n << ' ' << max_n << '\n';
return 0;
}复杂度
一次模拟是 V 是阈值上界。
总结
这题最重要的是把原问题抽象成一个单调函数:
count(n):阈值为n时能 AC 的题数
一旦看到它单调不增,后面就是很标准的“模拟 + 二分区间端点”。