用python解决需要用到前缀和与差分两种思路的题目
这些题目,都是差分加前缀和再加上一些其他的知识点,其他知识点千变万化,这类题目需要多加训练才能一眼看出需要用到差分与前缀和的思想。由于差分与前缀和已经在前面讲过,所以下面的题目只会在程序中简单解释。
题目链接
1. 阿坤老师的课堂挑战
代码如下:
n,q = map(int,input().split())
a = [0]*(n+2)
b = []
# 求差分
for _ in range(q):
l,r,x = map(int,input().split())
a[l]+=x
a[r+1]-=x
b.append([l,r,x])
# 求前缀和
for i in range(1,n+1):
a[i]+=a[i-1]
# 找出最大值
maxv = max(a)
# 找出下标
p = a.index(maxv)
maxx = 0
# 判断最大值是否在边界里
for l,r,x in b:
if l<=p<=r:
maxx=max(maxx,x)
print(maxv-maxx)
2. 大衣的最大排序和
n,q = map(int,input().split())
a = list(map(int,input().split()))
a.sort(reverse=True)
b = [0]*(n+2)
# 差分前缀和来存a的第i个位置被加了几次
# 差分
for _ in range(q):
l,r = map(int,input().split())
b[l] += 1
b[r+1] -= 1
# 前缀和
for i in range(1,n+1):
b[i] += b[i-1]
# v是位置,p是值
c = [(v,p)for v,p in enumerate(b[1:-1],1)]
# 按值与字典值逆序排序
# 题目说按字典值正序排序,但此处按逆序是因为a列表是逆序,这样通过下面赋值的操作就能使最后的结果为正序
c.sort(key=lambda x:(-x[1],-x[0]))
# ans记录最大值
ans =0
# res记录最小字典序
res = [0]*n
for x,(v,p) in zip(a,c):
ans += x*p
# 这里p-1是因为前面enumerate从1开始记录下标
res[v-1] = x
print(ans)
print(*res)
3. 充能计划
n, m, q = map(int, input().split())
s = [0] + list(map(int, input().split()))
lst = [list(map(int, input().split())) for _ in range(q)]
# 可以将充能的列表排序,这样可以用每个种类的宝石不断从当前位置往后充能,能够避免同一类宝石重复充能某个位置
lst.sort()
print(lst)
diff = [0] * (n+2) # 差分数组
idx = 0; ind = 0 # 当前何种能量 处在何处
for p, k in lst:
if p != idx: # 开始下一个能量的计算
idx = p; ind = k - 1
if ind >= k + s[p] - 1 or ind >= n: # 能量相当于没加
continue
left = max(ind+1, k); right = min(k + s[p] - 1, n)
# 将[left, right]整体加1
diff[left] += 1; diff[right+1] -= 1
ind = right
# 前缀和
score = 0
for i in range(1, n+1):
score += diff[i]
print(score, end = " ")
4. 推箱子
这题也是要用到前缀和的,但是与之前的不太一样,放在这主要是为下题做准备
推箱子代码如下:
思路:以中间箱子(数量中间数)为基准,左边箱子向右移动,右边箱子向左移动
# 前两句为批量读取数据
from sys import stdin
input = stdin.readline
# t个关卡
t = int(input().strip())
for _ in range(t):
# n表示可用位子情况
s = int(input().strip())
# a表示去除前后空位置之后的箱子
# 因为前后空位置对移动箱子的次数没有影响
a = input().strip().strip("_")
# n表示有几个箱子与空位
n = len(a)
# m表示有几个箱子
m = a.count("@")
# 计数,计数有几个箱子,方便找箱子的中位值
cnt = 0
# 找箱子的中位值
for i, c in enumerate(a):
if c == "@":
cnt += 1
if cnt == (m + 1) // 2:
mid = i
break
# ans表示共需要移动几步
ans = 0
# 移动遇到的空格数
space = 0
# 左边箱子向右移动
for i in range(mid - 1, -1, -1):
if a[i] == "_":
space += 1
if a[i] == "@":
ans += space
space = 0
# 右边箱子向左移动
for i in range(mid + 1, n):
if a[i] == "_":
space += 1
if a[i] == "@":
ans += space
print(ans)
5. 大石头的搬运工
代码如下:
思想:一堆石头为基准,左边箱子向右移动,右边箱子向左移动,最后遍历看移动到那个箱子的左右值最小
# 推箱子
n = int(input())
a, b = [], []
li = []
#遍历循环输入的n组w和p
for i in range(n):
li.append(list(map(int,input().split())))
# 先排序,之后才能够合并了再一起移动
li.sort(key=lambda x:x[1])
for x,y in li:
a.append(x)
b.append(y)
left = [0]*n
left[0]=a[0]
for i in range(1,n):
left[i] = a[i] + left[i-1]
# 从右往左计算前缀和
right=[0]*n
right[-1]=a[-1]
for i in range(n-2,-1,-1):
right[i]=a[i]+right[i+1]
custL =[0]*n
for i in range(1,n):
custL[i] = custL[i-1]+left[i-1]*(b[i]-b[i-1])
custR = [0]*n
for i in range(n-2,-1,-1):
custR[i] = custR[i+1]+right[i+1]*(b[i+1]-b[i])
ans = float('inf')
for i in range(n):
ans = min(ans,custL[i]+custR[i])
print(ans)