语文成绩

GitHub跳转原题关系图返回列表

把多次区间加分转成差分数组的两个端点修改,最后前缀还原并维护最低成绩。

OJ: luogu

题目 ID: P2367

难度:普及-

标签:差分前缀和模拟python

日期: 2026-06-18 19:16

题意

n 个学生,每个学生有一个初始语文成绩。

接下来有 p 次修改,每次给出 l r z,表示把第 l 到第 r 个学生的成绩都增加 z

要求输出所有修改完成后,全班最低的语文成绩。

思路

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

int a[10005];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, p;
    cin >> n >> p;

    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    for (int i = 1; i <= p; i++) {
        int l, r, z;
        cin >> l >> r >> z;
        // 朴素做法:把区间里的每个学生成绩都加上 z。
        for (int j = l; j <= r; j++) {
            a[j] += z;
        }
    }

    int ans = a[1];
    for (int i = 2; i <= n; i++) {
        if (a[i] < ans) ans = a[i];
    }

    cout << ans << '\n';
    return 0;
}

朴素做法每次修改都枚举 [l,r] 中的所有学生。它很容易理解,但 np 都可能达到 5 * 10^6,最坏情况下无法通过。

这题的操作都是“连续区间整体加一个数”,并且中间没有查询,最后才求结果。按照 rbook《差分》文章中的模型,这正是普通差分适用的场景。

设差分数组为:

text
diff[i] = a[i] - a[i-1]

如果要让 [l,r] 每个数都加 z,只需要:

text
diff[l] += z
diff[r+1] -= z

因为前缀和还原时,从 l 开始都会多累加到 z,从 r+1 开始这个 z 又被抵消。

一次修改的影响

这张表展示一次 [l,r]z 对不同位置的影响。

位置范围 前缀和中是否包含 +z 前缀和中是否包含 -z 最终变化
i < l 不变
l <= i <= r 增加 z
i > r 抵消,不变

读入初始成绩时,也可以把第 i 个成绩看成一次 [i,i] 的区间加法,直接写进同一个差分数组。

最后从左到右做前缀和还原每个学生的最终成绩,同时维护最小值即可。

Python 知识

  • array("i") 用连续的 32 位整数保存五百万项差分,避免 Python list[int] 的对象开销。
  • os.read 分块解析整数,不会让超长输入行的 split() 瞬间创建数百万个字节串。
  • islice(difference, n) 只遍历有效部分,不复制大数组。

代码

python
import os
from array import array
from itertools import islice


def read_ints():
    number = 0
    sign = 1
    reading = False
    while chunk := os.read(0, 1 << 20):
        for byte in chunk:
            if 48 <= byte <= 57:
                number = number * 10 + byte - 48
                reading = True
            else:
                if reading:
                    yield sign * number
                    number = 0
                    sign = 1
                    reading = False
                elif byte == 45:
                    sign = -1
    if reading:
        yield sign * number


data = iter(read_ints())
n, operations = next(data), next(data)
difference = array("i", [0]) * (n + 1)
previous = 0
for i in range(n):
    score = next(data)
    difference[i] = score - previous
    previous = score

for _ in range(operations):
    left, right, change = next(data), next(data), next(data)
    difference[left - 1] += change
    difference[right] -= change

score = 0
answer = 1 << 60
for change in islice(difference, n):
    score += change
    answer = min(answer, score)
print(answer)

复杂度

  • 建立差分数组需要 O(n)O(n)
  • 每次修改只改两个端点,p 次修改需要 O(p)O(p)
  • 最后还原并求最小值需要 O(n)O(n)
  • 总时间复杂度 O(n+p)O(n + p)
  • 空间复杂度 O(n)O(n)

总结

这题是“一维差分”的直接应用。

看到大量区间加法,并且所有操作结束后才查询最终结果,就应该先想到差分。差分的作用是把一次区间修改压缩成两个端点修改,最后再用一次前缀和把所有影响还原出来。