{ }codelper
前缀和二维列表64 位整数P0092

快速求网格中矩形的和

一个 r 行 c 列的网格,每格写有一个整数。
最上面一行是第 1 行,最左边一列是第 1 列。
给出 q 个询问,每个询问用四个数 r₁ c₁ r₂ c₂ 询问「由第 r₁~r₂ 行、第 c₁~c₂ 列(含两端)组成的矩形内所有数之和」。
按给出的顺序,每个询问输出一行答案。
第一行给出 r、c、q,接下来 r 行给出每行的 c 个数,再接下来 q 行给出询问,都用空格隔开。

输入
1 ≤ r, c ≤ 100,1 ≤ q ≤ 10,000,每格的数在 -10⁶ 到 10⁶ 之间,1 ≤ r₁ ≤ r₂ ≤ r,1 ≤ c₁ ≤ c₂ ≤ c
输出
q 行,每个询问的矩形之和
样例 1 · stdin
3 4 3
1 2 3 4
5 6 7 8
9 10 11 12
1 1 3 4
2 2 3 3
1 4 3 4
stdout
78
34
24
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

每个询问都重新把格子加一遍,最多是一万个询问 × 一万格 = 一亿次。
Python 无法在时限内完成,Craykon 会超过执行步数上限(约五千万)而停止。
先用 P[i][j] = a[i][j] + P[i-1][j] + P[i][j-1] - P[i-1][j-1] 建一次二维前缀和 P[i][j] =「从 (1,1) 到 (i,j) 的矩形之和」(第 0 行、第 0 列为 0),答案就是 P[r₂][c₂] - P[r₁-1][c₂] - P[r₂][c₁-1] + P[r₁-1][c₁-1]。
三个陷阱:用 r₁、c₁ 代替 r₁-1、c₁-1 来减,会漏掉最上面一行和最左边一列。
容易忘记把被减了两次的左上角部分加回来。
另外和最大为 10⁴ × 10⁶ = 10¹⁰,在 C、C++、Java、C# 中放进 int(约二十一亿)会溢出——要用 long/long long。
Python 的整数没有大小上限,JavaScript、TypeScript、Craykon 在 2^53 以内是精确的,所以没问题。
创建二维数组各语言不同:Python [[0] * (c + 1) for _ in range(r + 1)]([[0] * (c + 1)] * (r + 1) 是错的:每一行都是同一个列表),JS Array.from({ length: r + 1 }, () => new Array(c + 1).fill(0)),Java new long[r + 1][c + 1](自动填 0),C# 矩形数组 new long[r + 1, c + 1] 配 P[i, j],C 全局的 long long P[101][101],C++ vector<vector<long long>>。
Craykon 每行用 P.添加(…) 放入一个 新列表(c + 1, 0)——每行只有 101 格,远低于列表上限(10,000 个)。

Pythonmain.py · 19 行
import sys data = sys.stdin.buffer.read().split()r, c, q = int(data[0]), int(data[1]), int(data[2])pos = 3 # P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0으로 둔다P = [[0] * (c + 1) for _ in range(r + 1)]for i in range(1, r + 1):    for j in range(1, c + 1):        P[i][j] = int(data[pos]) + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1]        pos += 1 out = []for _ in range(q):    r1, c1, r2, c2 = map(int, data[pos:pos + 4])    pos += 4    out.append(P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1])print("\n".join(map(str, out)))
JavaScriptmain.js · 19 行
const t = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [r, c, q] = t;let pos = 3; // P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0으로 둔다const P = Array.from({ length: r + 1 }, () => new Array(c + 1).fill(0));for (let i = 1; i <= r; i++) {  for (let j = 1; j <= c; j++) {    P[i][j] = t[pos++] + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];  }} const out = [];for (let k = 0; k < q; k++) {  const [r1, c1, r2, c2] = t.slice(pos, pos + 4);  pos += 4;  out.push(P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1]);}console.log(out.join("\n"));
TypeScriptmain.ts · 20 行
import { readFileSync } from "node:fs";const t: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [r, c, q] = t;let pos = 3; // P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0으로 둔다const P: number[][] = Array.from({ length: r + 1 }, () => new Array(c + 1).fill(0));for (let i = 1; i <= r; i++) {  for (let j = 1; j <= c; j++) {    P[i][j] = t[pos++] + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];  }} const out: number[] = [];for (let k = 0; k < q; k++) {  const [r1, c1, r2, c2] = t.slice(pos, pos + 4);  pos += 4;  out.push(P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1]);}console.log(out.join("\n"));
JavaMain.java · 30 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.StringTokenizer; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(br.readLine());        int r = Integer.parseInt(st.nextToken()), c = Integer.parseInt(st.nextToken()), q = Integer.parseInt(st.nextToken());         // P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0(자바 배열은 0으로 채워진다)        long[][] P = new long[r + 1][c + 1];        for (int i = 1; i <= r; i++) {            st = new StringTokenizer(br.readLine());            for (int j = 1; j <= c; j++) {                P[i][j] = Long.parseLong(st.nextToken()) + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];            }        }         StringBuilder out = new StringBuilder();        for (int k = 0; k < q; k++) {            st = new StringTokenizer(br.readLine());            int r1 = Integer.parseInt(st.nextToken()), c1 = Integer.parseInt(st.nextToken());            int r2 = Integer.parseInt(st.nextToken()), c2 = Integer.parseInt(st.nextToken());            out.append(P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1]).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 26 行
using System;using System.Text; class Program{    static void Main()    {        string[] t = Console.In.ReadToEnd().Split((char[])null, StringSplitOptions.RemoveEmptyEntries);        int r = int.Parse(t[0]), c = int.Parse(t[1]), q = int.Parse(t[2]);        int pos = 3;         // P[i, j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0        long[,] P = new long[r + 1, c + 1];        for (int i = 1; i <= r; i++)            for (int j = 1; j <= c; j++)                P[i, j] = long.Parse(t[pos++]) + P[i - 1, j] + P[i, j - 1] - P[i - 1, j - 1];         var sb = new StringBuilder();        for (int k = 0; k < q; k++, pos += 4)        {            int r1 = int.Parse(t[pos]), c1 = int.Parse(t[pos + 1]), r2 = int.Parse(t[pos + 2]), c2 = int.Parse(t[pos + 3]);            sb.Append(P[r2, c2] - P[r1 - 1, c2] - P[r2, c1 - 1] + P[r1 - 1, c1 - 1]).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 24 行
#include <iostream>#include <vector> int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int r, c, q;    std::cin >> r >> c >> q;     // P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0으로 둔다    std::vector<std::vector<long long>> P(r + 1, std::vector<long long>(c + 1, 0));    for (int i = 1; i <= r; i++) {        for (int j = 1; j <= c; j++) {            long long v;            std::cin >> v;            P[i][j] = v + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];        }    }    for (int k = 0; k < q; k++) {        int r1, c1, r2, c2;        std::cin >> r1 >> c1 >> r2 >> c2;        std::cout << P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1] << '\n';    }}
Cmain.c · 24 行
#include <stdio.h> #define MAX 100 /* P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0(전역 배열은 0으로 시작) */static long long P[MAX + 1][MAX + 1]; int main(void) {    int r, c, q;    scanf("%d %d %d", &r, &c, &q);    for (int i = 1; i <= r; i++) {        for (int j = 1; j <= c; j++) {            long long v;            scanf("%lld", &v);            P[i][j] = v + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];        }    }    for (int k = 0; k < q; k++) {        int r1, c1, r2, c2;        scanf("%d %d %d %d", &r1, &c1, &r2, &c2);        printf("%lld\n", P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1]);    }    return 0;}
Craykonmain.cray · 29 行
첫줄 = 控制台.读数字();r = 첫줄[0];c = 첫줄[1];q = 첫줄[2]; // P[i][j] = 왼쪽 위 (1,1)부터 (i,j)까지 직사각형의 합. 0번째 줄·칸은 0으로 둔다P = [];循环 (i = 0; i <= r; i++){    P.添加(新列表(c + 1, 0));}循环 (i = 1; i <= r; i++){    row = 控制台.读数字();    循环 (j = 1; j <= c; j++)    {        P[i][j] = row[j - 1] + P[i - 1][j] + P[i][j - 1] - P[i - 1][j - 1];    }} 循环 (k = 0; k < q; k++){    qq = 控制台.读数字();    r1 = qq[0];    c1 = qq[1];    r2 = qq[2];    c2 = qq[3];    控制台.输出(P[r2][c2] - P[r1 - 1][c2] - P[r2][c1 - 1] + P[r1 - 1][c1 - 1]);}