本题我没有AC,最高得分55。翻看了Python提交记录发现没有一个能够AC,最高分为70,我认为应该是洛谷的问题,Python本身相比较C++来说就比较慢,翻遍全网也没有找到此题的Python详细的代码,故学习洛谷C++大佬的思路,编写Python代码,写下此篇记录一下学习过程,加之抛砖引玉,希望有大佬给出分数相对较高的代码。
题目链接:https://www.luogu.com.cn/problem/P1083
本题有两种算法:暴力法 和 二分答案 + 差分数组
1.暴力法
遍历订单天数,维护一个数组,用于添加天数,每次循环一个订单累加需求并与原有教室数量进行比较,若大于则停止循环记录订单编号输出结果,此法最高40分。
# 输入数据
n, m = map(int, input().split())
rooms = list(map(int, input().split()))
classroom = [0] * n
bill = []
flag = 0
arr = [0] * (n + 1)
for i in range(m):
bill.append(list(map(int, input().split())))
for i in range(m): # 循环订单
for j in range(bill[i][1], bill[i][2] + 1):
classroom[j - 1] += bill[i][0]
for a in range(n):
if classroom[a] > rooms[a]:
flag = i + 1 # 记录订单编号
break
if flag != 0:
break
if flag == 0:
print(0)
else:
print(-1)
print(flag)
2.二分+差分
对于其提供的订单结果来看,“我们要按照订单的先后顺序依次为每份订单分配教室”,由此句我们可以得知当中间的一份订单无法满足时,后续的所有订单也不能够满足,我们可以使用二分答案,对所给的订单进行二分取舍。
后面的问题是二分后如何判断该订单是否能够满足?因为每个订单后面都规定了相应的范围,可以用差分来简便计算。
先简单介绍一下什么是差分数组,差分就是前缀和的逆序,先举个例子:
设有数组 arr = [1, 3, 7, 5, 2]
我们可以得出每个元素之间的差 d = [1, 2, 4, -2, -3]
当我们对d前n项求和是可以发现 sum = [1, 3, 7, 5, 2],sum居然 == arr,这就是其性质。
对此我们有相应的公式:
假设我们要对arr中[2, 4]范围中的数+2,对[1, 3]范围中的数-1,利用上述公式我们可以得到
此时d更新为 d = [1, 1, 6, -1, -3](对于越界的数可以不用管,也可以后续append(0) )
此时再将数组d前n项求和发现与你算的结果相同: ans = [1, 2, 8, 6, 4]
那我们为什么要在(R+1)后面再减去这个数呢?假设d = [0, 0, 0, 0, 0],若对[1, 3]范围-1会得到d = [0, -1, 0, 0, 0],因为我们最后要进行累加,进行前缀和会变成 d = [0, -1, -1, -1, -1],此时我们发现位置4也进行了一次-1操作,所以我们要在(R+1)的位置进行减去操作。
下面上代码:
# 输入数据
n, m = map(int, input().split())
rooms = list(map(int, input().split()))
classroom = [0] * (n + 1) # 设立n+1是为了防止越界,因为存在前文所说的(R+1)项
bill = []
arr = [0] * (n + 1) # 设立n+1是为了后续判别方便第一项arr[i - 1](i = 1)累加
for i in range(m):
bill.append(list(map(int, input().split())))
# 进行判断
def diff(x): # 判断第x号订单是否符合条件
classroom = [0] * (n + 1) # 差分数组全置0
for i in range(x): # 补充差分数组(注意数组越界问题)
classroom[bill[i][1] - 1] += bill[i][0] # 套公式对左边界进行 + 1
classroom[bill[i][2]] -= bill[i][0] # 套公式对右边界进行 - 1
for i in range(1, n + 1): # 开始判断(注意数组越界问题)
arr[i] = arr[i - 1] + classroom[i - 1] # 进行前缀和
if arr[i] > rooms[i - 1]: # 请求数量>所有,订单失效
return 0
return 1
left = 0
right = m
if diff(right): # 先判断最后一个订单是否能够完成
print(0)
else:
while left <= right: # 对订单进行二分
mid = (left + right) // 2
if diff(mid): # 此订单可以,代表前面所有订单都可以执行,向后二分
left = mid + 1
else: # 此订单不可,代表后面的订单全部失效,向前二分
right = mid - 1
print(-1)
print(left)