收集晨露

错误代码:
check(mid) 函数在每次二分查找时都循环求 mid 个元素的和,而 mid 最大可以是 n。
这样,一次 check 最坏是 O(n),一次二分查找复杂度 O(nlog n),q 次询问就是 O(nqlog n),必然超时(10^5 * 10^5 量级)。
python
n, q = map(int, input().split())
a = list(map(int, input().split()))
def check(y):
num = 0
for i in range(y):
num += a[i]
if num >= x:
return True
else:
return False
for _ in range(q):
x = int(input())
left, right = 1, n
ans = 0
flag = False
while left <= right:
mid = (left + right) // 2
if check(mid):
ans = mid
flag = True
right = mid - 1
else:
left = mid + 1
print(ans if flag else -1)
实际上,需要多次求前缀和,可以用前缀和数组把 check 降到 O(1)。
正确代码如下:
python
n, q = map(int, input().split())
a = list(map(int, input().split()))
prev = [0] * n
prev[0] = a[0]
for i in range(1, n):
prev[i] = prev[i - 1] + a[i]
for _ in range(q):
x = int(input())
left, right = 0, n - 1
ans = -1
while left <= right:
mid = (left + right) // 2
if prev[mid] >= x:
ans = mid + 1#因为索引从0开始,所以叶子数要+1
right = mid - 1
else:
left = mid + 1
print(ans)
智慧农场灌溉

代码如下:
python
n, v = map(int, input().split())
w = list(map(int, input().split()))
def check(y):
total = 0
for x in w:
total += min(x, y)
if total <= v:
return True
else:
return False
left, right = 1, 1000000000000
ans = 0
while left <= right:
mid = (left + right) // 2
if check(mid):
ans = mid
left = mid + 1
else:
right = mid - 1
print(ans)
博物馆奇妙之旅

代码如下:
python
n, q = map(int, input().split())
a = list(map(int, input().split()))
prev = [0] * n
prev[0] = a[0]
for i in range(1, n):
prev[i] = prev[i - 1] + a[i]
def get_prev(prev, l, r):
if l == 0:
return prev[r]
else:
return prev[r] - prev[l - 1]
for _ in range(q):
l, t = map(int, input().split())
l_act = l - 1
left, right = l - 1, n - 1
ans = -1
while left <= right:
mid = (left + right) // 2
if get_prev(prev, l_act, mid) >= t:
ans = mid + 1
right = mid - 1
else:
left = mid + 1
print(ans)
爱心图书室

代码如下:
python
import sys
def solve():
data = sys.stdin.buffer.read().split()#快速输入
it = iter(data)
n = int(next(it))
q = int(next(it))
a = [int(next(it)) for _ in range(n)]
#前缀和
prev = [0] * n
prev[0] = a[0]
for i in range(1, n):
prev[i] = prev[i - 1] + a[i]
for _ in range(q):
m = int(next(it))
left, right = 0, n - 1
ans = 0
while left <= right:
mid = (left + right) // 2
if prev[mid] <= m:
ans = mid + 1
left = mid + 1
else:
right = mid - 1
print(ans)
if __name__ == '__main__':
solve()
寻找完美假期

代码如下:
python
import sys
def solve():
data = sys.stdin.buffer.read().split()
it = iter(data)
n = int(next(it))
a = int(next(it))
b = int(next(it))
t = [int(next(it)) for _ in range(n)]
left, right = 0, 0
ans = 0
tot = 0
#注意不能加上and right < n,当 right == n 时,说明已经处理完所有元素,但还可以继续移动 left
#left 控制窗口左边界,必须遍历完所有可能的左边界
#right 记录已经探索到的位置,可以提前到达末尾
while left < n:
# 扩大右边界直到和 >= a
while right < n and tot < a:
tot += t[right]
right += 1
# 如果找到了符合条件的窗口
if tot >= a:
current_tot = tot
current_right = right
# 探索所有可能的右边界
#用 <= n 确保:统计最后一个窗口:当 current_right = n 时,还能进入循环统计
while current_tot <= b and current_right <= n:
ans += 1
#不能省略该if条件,因为当 current_right == n 时,current_right[r] 是索引越界的
if current_right < n:
current_tot += t[current_right]
current_right += 1
# 移动左指针
tot -= t[left]
left += 1
print(ans)
if __name__ == '__main__':
solve()
摄影师的完美相册

代码如下:
python
import sys
def solve():
data = sys.stdin.buffer.read().split()
it = iter(data)
n = int(next(it))
q = int(next(it))
p = [int(next(it)) for _ in range(n)]
prev = [0] * n
prev[0] = p[0]
for i in range(1, n):
prev[i] = prev[i - 1] + p[i]
def get_sum(prev, a, b):
if a == 0:
return prev[b]
else:
return prev[b] - prev[a - 1]
for _ in range(q):
l = int(next(it))
k = int(next(it))
l_act = l - 1
left, right = l_act, n - 1
num = 0
while left <= right:
mid = (left + right) // 2
if get_sum(prev, l_act, mid) <= k:
num = mid + 1
left = mid + 1
else:
right = mid - 1
print(num - l_act if num - l_act > 0 else 0)
if __name__ == '__main__':
solve()
快乐登山步数计

代码如下:
python
import sys
def solve():
data = sys.stdin.buffer.read().split()#快速输入
it = iter(data)
n = int(next(it))
q = int(next(it))
a = [int(next(it)) for _ in range(n)]
#前缀和
prev = [0] * n
prev[0] = a[0]
for i in range(1, n):
prev[i] = prev[i - 1] + a[i]
for _ in range(q):
h = int(next(it))
left, right = 0, n - 1
ans = 0
flag = False
while left <= right:
mid = (left + right) // 2
if prev[mid] >= h:
ans = mid + 1
flag = True
right = mid - 1
else:
left = mid + 1
print(ans if flag else "Target Unreachable")
if __name__ == '__main__':
solve()
城市最美花圃

纯暴力解法:(会超时)
python
import sys
def solve():
data = sys.stdin.buffer.read().split()
it = iter(data)
n = int(next(it))
k = int(next(it))
a = [int(next(it)) for _ in range(n)]
# 前缀和
prefix = [0] * n
prefix[0] = a[0]
for i in range(1, n):
prefix[i] = prefix[i - 1] + a[i]
max_avg = 0
# 枚举所有可能的左端点
for left in range(n):
# 枚举所有可能的右端点(保证长度 >= k)
for right in range(left + k - 1, n):
if left == 0:
total = prefix[right]
else:
total = prefix[right] - prefix[left - 1]
length = right - left + 1
avg = total // length # 向下取整
if avg > max_avg:
max_avg = avg
print(max_avg)
if __name__ == '__main__':
solve()
正确代码如下:
python
import sys
def solve():
data = sys.stdin.buffer.read().split()
it = iter(data)
n = int(next(it))
k = int(next(it))
a = [int(next(it)) for _ in range(n)]
def check(avg):
# 检查是否存在长度 >= k 的子段,平均高度 >= avg
# 将问题转化为:是否存在子段和 >= avg * 长度
# 即 sum(a[i]) - avg * len >= 0
# 令 b[i] = a[i] - avg
# 找长度 >= k 的最大子段和是否 >= 0
prefix = [0] * n
prefix[0] = a[0] - avg
for i in range(1, n):
prefix[i] = prefix[i - 1] + (a[i] - avg)
# 找长度 >= k 的最大子段和
# 修改3:调整区间和的计算逻辑
# 对于右端点i,左端点可以是 -1 到 i-k
# 使用一个变量记录左端点的最小值
min_prefix = 0 # 对应左端点为-1的情况(即从0开始)
# 需要同时考虑左端点为-1的情况(即prefix[-1] = 0)
# 所以对于右端点 i,考虑所有可能的左端点 j ∈ [-1, i-k]
# 其中 prefix[-1] 视为 0
for i in range(k - 1, n):#右端点
# 更新可能的左端点最小值
# 当 i = k-1 时,左端点可以是 -1
# 当 i > k-1 时,左端点可以是 i-k
if i > k - 1:
min_prefix = min(min_prefix, prefix[i - k])
# 检查以i为右端点的子段和是否 >= 0
if prefix[i] - min_prefix >= 0:
return True
# 还需要检查一种特殊情况:从0开始的长度为k的子段
# 这已经被上面的循环覆盖了,因为当 i = k-1 时,min_prefix=0,检查的就是 prefix[k-1] >= 0
return False
# 二分答案
left, right = 1, max(a)
ans = 0
while left <= right:
mid = (left + right) // 2
if check(mid):
ans = mid
left = mid + 1
else:
right = mid - 1
print(ans)
if __name__ == '__main__':
solve()
为什么是prefix[i-k]?
当右端点是i时,新的可能的左端点是i-k+1,对应的left-1就是i-k,所以需要把这个新的前缀和纳入最小值候选