【专题】前缀和+差分

【专题】前缀和+差分

一:前缀和

1.1. (一维)前缀和【模版】

核心:【区间内值和

题目描述

给定一个长度为n的数组a1,a2,…ana_1, a_2,…a_na1,a2,…an.

接下来有q次查询, 每次查询有两个参数l, r.

对于每个询问, 请输出al+al+1+…+ara_l+a_{l+1}+…+a_ral+al+1+…+ar

链接:https://ac.nowcoder.com/acm/problem/226282
来源:牛客网

基本思路

n,m=list(map(int,input().split()))
a=list(map(int,input().split()))

# 预处理:前缀和数组(当前项+前面所有项和)
pre=[0]*(n+1) # pre[0] 默认为0
for i in range(1,n+1):
    pre[i]=pre[i-1]+a[i-1]

# 实际操作
for i in range(m):
    l,r=list(map(int,input().split()))
    print(pre[r]-pre[l-1])
-----------------------------------
输入:
	3 2
    1 2 4
    1 2
    2 3
输出:
	3
    6
-----------------------------------
类题:Tokitsukaze and Average of Substring

题目描述

Tokitsukaze 有一个只含小写字母的字符串 s,长度为 n。

若si=sj (l≤i<j≤r),则称 si​ 与 sj​ 为一对相同字符。定义 C(l,r) 为 字符串 [l,r] 的子串中相同字符的对数。

例如对于字符串 ``aaabab’',C(1,1)=0C(1,1)=0C(1,1)=0, C(1,2)=1C(1,2)=1C(1,2)=1, C(1,3)=3C(1,3)=3C(1,3)=3, C(3,5)=1C(3,5)=1C(3,5)=1, C(1,6)=7C(1,6)=7C(1,6)=7 (有 666 对 ‘a’ 以及 111 对 ‘b’`)。

定义 F(l,r)=C(l,r)(r−l+1)F(l,r)=\frac{C(l,r)}{(r-l+1)}F(l,r)=(r−l+1)C(l,r)​。Tokitsukaze 想知道对于 sss 的所有子串,F(l,r)F(l,r)F(l,r) 的最大值是多少。

链接:https://ac.nowcoder.com/acm/contest/51958/C
来源:牛客网

m = int(input())

# 26个字母对应的前缀和(范围放大)
pre = [[0] * (100000) for i in range(30)]

for _ in range(m):
    n = int(input())
    s = input()

    # 预处理:前缀和数组(每一个字母而言:当前项+前面所有项和)
    for i in range(1, n + 1):  # 遍历每一个元素
        cnt = ord(s[i - 1]) - ord('a') + 1  # 将字母转为数字处理
        # 遍历26个字母
        for j in range(1, 27):
            # pre[j][i] 就表示字母 j 在 s 的前 i 个字符中出现的次数
            # 存在字母
            if j == cnt:
                pre[j][i] = pre[j][i-1] + 1
            # 不存在字母
            else:
                pre[j][i] = pre[j][i - 1]

    # 实际操作
    ans = 0
    for i in range(1, n + 1): # 遍历所有可能的左端点l
        for j in range(i + 1, n + 1): # 遍历所有可能的右端点r(在l的右侧)
            l = i
            r = j
            cnt = 0 # 初始化当前子串中的字符对数为0
            for k in range(1, 27):
                # 使用前缀和数组快速计算字母k在子串[l, r]中的出现次数
                tmp = pre[k][r] - pre[k][l - 1]
                 # 根据出现次数计算字符对数,并累加到cnt中
                cnt += tmp * (tmp - 1) / 2 # 【对数公式:字母形成的对数】
            # 计算F(l,r)函数的最大值
            ans = max(float(ans), cnt / float(r - l + 1)) # 寻找最大值
    print("%.6f" % ans)

    # 重置前缀和数组,处理下一组数据
    for i in range(1, 27):
        for j in range(1, n + 1):
            pre[i][j] = 0
---------------------------------------------
输入:
	4
    5
    aabab
    1
    a
    6
    aaabab
    11
    teeqtqrqwwe
输出:
	0.800000
    0.000000
    1.200000
    0.727273
---------------------------------------------
类题:左小数

给定由n个正整数组成的序列,问在序列中有多少个数,满足在它左边的所有数都比它小。

def count_dominant_elements(n, nums):
    leftMax = 0  # 初始化当前扫描过的最大值
    result = 0  # 初始化结果计数器
    
    for num in nums:
        if num > leftMax:  # 如果当前元素大于leftMax,则满足条件
            result += 1
        leftMax = max(leftMax, num)  # 更新leftMax为当前扫描过的最大值
    
    return result

# 输入处理
n = int(input())
nums = list(map(int, input().split()))

# 输出结果
print(count_dominant_elements(n, nums))
--------------------------------------------
输入:
	4
    1 3 2 4
输出:
	3
--------------------------------------------
类题:中间数❤

给定由n个正整数组成的序列,问在序列中有多少个数,满足在它左边的所有数都比它小、且在它右边的所有数都比它大。

def find_special_numbers(n, a):
    # 初始化前缀最大值数组
    leftMax = [0] * n
    leftMax[0] = a[0]
    
    # 计算前缀最大值
    for i in range(1, n):
        leftMax[i] = max(leftMax[i - 1], a[i])
    
    # 初始化后缀最小值
    rightMin = float('inf')
    result = 0
    
    # 从右向左遍历,计算后缀最小值并判断
    for i in range(n - 1, -1, -1):
        if (i == 0 or leftMax[i - 1] < a[i]) and a[i] < rightMin:
            result += 1
        rightMin = min(rightMin, a[i])
    
    return result

# 读取输入
n = int(input())
a = list(map(int, input().split()))

# 输出结果
print(find_special_numbers(n, a))
---------------------------------------------
输入:
	5
    1 2 6 5 8
输出:
	3
---------------------------------------------
类题:012对

给定由n个0或1或2组成的序列,我们把序列中从左至右(可不连续)存在的0、1、2称为012对,问在序列中有多少个012对

假设5个数的下标分别是1、2、3、4、5。

1号位的0、2号位的1、5号位的2可以组成012对;

1号位的0、4号位的1、5号位的2可以组成012对;

3号位的0、4号位的1、5号位的2可以组成012对;

所以共3个012对。

n=int(input())
# 返回一个list数列
a=list(map(int,input().split()))

count_0=0
count_01=0
result=0

for num in a:
    if num==0:
        count_0+=1
    elif num==1:
        count_01+=count_0
    elif num==2:
        result+=count_01


print(result)
-----------------------------
输入:
	5
	0 1 0 1 2
输出:3
-----------------------------

1.2. (二维)前缀和【模版】

基本思路

题目描述

给你一个 n 行 m 列的矩阵 A ,下标从1开始。
接下来有 q 次查询,每次查询输入 4 个参数 x1 , y1 , x2 , y2
请输出以 (x1, y1) 为左上角 , (x2,y2) 为右下角的子矩阵的和,

链接:https://ac.nowcoder.com/acm/problem/226333
来源:牛客网

就本题目而言:

n,m,q=list(map(int,input().split()))
a=[list(map(int,input().split())) for _ in range(n)]

 # 前缀和数组【二维】
pre = [[0] * (m + 2) for _ in range(n + 2)]

# 1、初始化
for i in range(1,n+1):
    for j in range(1,m+1):
        pre[i][j] = a[i-1][j-1] + pre[i][j-1] + pre[i-1][j] - pre[i-1][j-1]

for _ in range(q):
    x1,y1,x2,y2=list(map(int,input().split()))
    # 2、实际操作
    sum_number = pre[x2][y2] - pre[x2][y1-1] - pre[x1-1][y2] + pre[x1-1][y1-1]
    print(sum_number)
-------------------------------------------------
输入:
	3 4 3
    1 2 3 4
    3 2 1 0
    1 5 7 8
    1 1 2 2 # 坐标1
    1 1 3 3 # 坐标2
    1 2 3 4 # 坐标3
输出:
	8
    25
    32
-------------------------------------------------
类题:激光炸弹

题目描述

一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。

现在地图上有n(N ≤ 10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。 激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。 若目标位于爆破正方形的边上,该目标将不会被摧毁。

通俗理解:给定边长R,圈起来的正方形内的值之和要最大。

链接:https://ac.nowcoder.com/acm/problem/20032
来源:牛客网

n,r=list(map(int,input().split()))

MAX_COORD = 5010

# 实际的二维区域
a=[[0]*MAX_COORD for _ in range(MAX_COORD)]
for i in range(n):
    x,y,c=list(map(int,input().split()))
    # 从1开始
    a[x+1][y+1] += c # 所在的小正方形的价值

# 前缀和数组【二维】
pre = [[0] * (MAX_COORD) for _ in range(MAX_COORD)]

# 预处理
for i in range(1,5005):
    for j in range(1,5005):
        pre[i][j] = a[i][j] + pre[i-1][j] + pre[i][j-1] - pre[i-1][j-1]

# 实际操作
ans=0 # 最大值
for i in range(r,5005):
    for j in range(r,5005):
        x1=i-r+1
        y1=j-r+1
        x2=i
        y2=j
        temp = pre[x2][y2] - pre[x2][y1-1] - pre[x1-1][y2] + pre[x1-1][y1-1]
        ans=max(ans,temp)

print(ans)
----------------------------------
输入:
    2 1
    0 0 1
    1 1 1
输出:
	1
----------------------------------

二:差分

(一维)差分【模版】

差分是一种有效解决区间更新的数据处理技术。

基本定义:对于原来数组 a[n],其差分数组d[n]定义为:

(1)d[0]=a[0] (边界条件)

(2)d[i] = d[i] - d[i-1] (i>=1)

(3)原数组执行区间 [ l , r ] 都加活减 x 操作,对于差分数组而言:

  • 区间加上数x => d[ l ] += x ,d [ r + 1 ] −= x
  • 区间减去数x => d[ l ] -= x ,d [ r + 1 ] += x

题目描述

对于给定的长度为 n 的数组 {a1,a2,…,an},我们有 m 次修改操作,每一次操作给出三个参数 l,r,k,代表将数组中的 al,al+1,…,ar 都加上 k 。请你直接输出全部操作完成后的数组。

n, m = map(int, input().split())
a = list(map(int, input().split()))
a.insert(0, 0) # 扩充数组,前面添加一位0
d = [0] * (n + 100) # 扩大差分数组范围,以免越界

# 构建差分数组
for i in range(1, (n + 1)):
    d[i] = a[i] - a[i - 1]
# 区间上加法操作
for i in range(m):
    l, r, k = map(int, input().split())
    d[l] += k
    d[r + 1] -= k
# 还原数组
for i in range(1, (n + 1)):
    a[i] = a[i - 1] + d[i]

for i in range(1, len(a)):
    print(a[i], end = ' ')
-----------------------------------
输入:
	3 2
    1 2 3
    1 2 4
    3 3 -2
输出:
	5 6 1
-----------------------------------