本文目录
引言
题引
题目描述
输入
输出
样例输入
样例输出
解题过程
暴力求解
二维前缀和数组求解
[解读] 二维前缀和算法
本质
图解
代码表述
牛刀小试
题目描述
输入格式
输出格式
输入样例
输出样例
参考答案
今天在学校oj平台做题时,有一道题目老是时间超限,后询问学长得知需要算法减少时间开销,于是习得了一个快速计算给定一个二维数组,求子数组的数组和,即二维前缀和。感叹算法的精妙,立即舍弃午睡,连忙赶来记录一下,给自己再温故一次。
C语言二维数组a[M][N], 给定四个整数LX,LY,RX,RY, 定义函数f(LX,LY,RX,RY)求数组若干元素之和:
输入第一行是两个整数M和N, 表示二维数组a的大小, 0
随后有M行, 每行有N个整数,是数组a的数组元素值,值位于-1000到1000之间。
第M+2行是一个整数Q,表示有Q个查询
再随后有Q行,每行有四个空格分开的整数LX,LY,RX,RY。其中0<=LX<=RX
60%的数据, M,N<=20, Q<=20
40%的数据, Q=100000
总共输出Q行
对于每一行查询,输出f(LX,LY,RX,RY)的值。
3 2 -1 -2 -3 -4 -5 -6 3 0 0 0 1 1 1 2 1 1 0 2 1
-3 -10 -18
像我这种毫无基础的小白,就只会先想到翻译题目意思,循环暴力求解。于是乎就有了:
#include
int main(void)
{
int n, i, j, k;
scanf("%d %d", &n,&k);
int w, lx, ly, rx, ry;
int a[301][301];
for (i = 0; i < n; i++) {
for (j = 0; j < k; j++) {
scanf("%d", &a[i][j]);
}
}
scanf("%d", &w);
for (int z = 0; z < w; z++) {
int sum = 0;
scanf("%d %d %d %d", &lx, &ly, &rx, &ry);
for (int q = lx; q <= rx; q++) {
for (int p = ly; p <= ry; p++) {
sum = sum + a[q][p];
}
}
printf("%d\n", sum);
}
//LX,LY,RX,RY。其中0<=LX<=RX
评判机显然不买账,直接以“时间超限”为由把我编译的代码打回了。
在我询问班上acm队大佬时,我得到了她的一句话:“二维数组前缀和,又称矩阵和,搜一下这个算法,这道题用算法写”。
我在网上找了很久以及询问其他学长,终于搞懂了什么叫“二维数组前缀和”。于是乎经过一番捣腾,终于让测评姬收下我的代码,代码如下:
#include
int main(void)
{
int n, i, j, k;
scanf("%d %d", &n,&k);
int w, lx, ly, rx, ry;
int a[301][301];
int sum[301][301];
for (i = 1; i <= n; i++) {
for (j = 1; j <= k; j++) {
scanf("%d", &a[i][j]);
sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + a[i][j];
}
}
scanf("%d", &w);
for (int z = 0; z < w; z++) {
int re = 0;
scanf("%d %d %d %d", &lx, &ly, &rx, &ry);
/*for (int q = lx; q <= rx; q++) {
for (int p = ly; p <= ry; p++) {
sum = sum + a[q][p];
}
}*/
lx++; ly++; rx++; ry++;
re = sum[rx][ry] - sum[rx][ly-1] - sum[lx-1][ry] + sum[lx-1][ly-1];
printf("%d\n", re);
}
//LX,LY,RX,RY。其中0<=LX<=RX
这里就只增加二维前缀和数组sum。
看不懂?正常!别着急,且听我细细道来!
接下来我将从是什么,怎么来,怎么用三个角度来介绍二维前缀和算法。
二维前缀和实际上就是一个矩阵内值的和,而矩阵又可以由两个行数或列数少一的子矩阵组合后,删去重合部分再加上右下角的值共四个部分来构成。
话不多说,直接上图理解:
还没有理解吗?那在来看看这个例子 原二维数组a 与 二维前缀和数组S 之间的关系:
这样的好处是: 大大减少了原先通过两层for循环计算子数组所耗费的时间,只需要在插入数据时,对应的计算出二维前缀和数组,计算子矩阵的元素之和时不需要双层遍历,直接通过公式计算而的,这极大的减少了时间复杂度。
其更一般的算法表述如下:(代码源于网络)
#include
#include
#include
using namespace std;
const int N = 1010;
int a[N][N], s[N][N];
int n, m, q;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
cin >> q;
while (q--)
{
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1] << endl;
}
return 0;
}
Ok,是不是觉得不过瘾?来试试这一个类似简单的题目吧!
输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。对于每个询问输出子矩阵中所有数的和。
第一行包含三个整数 n,m,q。
接下来 n 行,每行包含 m 个整数,表示整数矩阵。
接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。
共 q 行,每行输出一个询问的结果。
3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4
17
27
21
#include
using namespace std;
const int N = 1010;
int n, m;
int q;
int a[N][N], s[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &q);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
scanf("%d", &a[i][j]);
}
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
s[i][j] = s[i][j-1] + s[i-1][j] - s[i-1][j-1] + a[i][j];
}
}
while(q --)
{
int x1, y1, x2, y2;
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
printf("%d\n", s[x2][y2] - s[x2][y1-1] - s[x1-1][y2] + s[x1-1][y1-1]);
}
return 0;
}
OK以上就是本文的所有内容了,如有错误欢迎指出讨论!