动窗法与前缀和——简单实践

题目是:

SJTUOJ 1002.二哥种花生

二哥在自己的后花园里种了一些花生,也快到了收获的时候了。这片花生地是一个长度为L、宽度为W的矩形,每个单位面积上花生产量都是独立的。他想知道,对于某个指定的区域大小,在这么大的矩形区域内,花生的产量最大会是多少。

首先尝试用最简单的遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>

using namespace std;

int cc(int **matp, int m, int l, int h);

int main()
{
int m, n, l, h;
cin >> m >> n;
int **matp = new int*[m];
for (int i = 0; i < m; i++)
{
*(matp + i) = new int[n];
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
cin >> matp[i][j];
}
}
cin >> l >> h;

int max = 0;
int temp = 0;
for (int i = 0; i < m-l+1; i++)
{
for (int j = 0; j < n-h+1; j++)
{
temp = cc(matp + i, j, l, h);
max = (temp >= max) ? temp : max;
}
}
cout << max;
return 0;
}

int cc(int **matp, int xa, int l, int h)
{
int ret = 0;
for (int i = 0; i < l; i++)
{
for (int j = 0; j < h; j++)
{
ret += matp[i][j + xa];
}
}
return ret;
}

发现反馈是6/10 [Time Limit Exceeded]

本身在搜索数组很大的时候,同一个数上要重新计算次,浪费了很多时间。

后来想到,可以采用动窗法来解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//constructed using some macro and snippets
#include <iostream>
using namespace std;

int main()
{
//using glide window method.
int mat_h, mat_l;
cin >> mat_h >> mat_l;

//DynMat Name:Pmat
//Lines:mat_h rows:mat_l
int **Pmat = new int *[mat_h];
for (int i = 0; i < mat_h; i++)
*(Pmat + i) = new int[mat_l];
int *Pmat_cfg = new int(mat_h);
//End of Dynmat.

for (int i = 0; i < mat_h; i++)
for (int j = 0; j < mat_l; j++)
cin >> Pmat[i][j];
int srch_l, srch_h;
cin >> srch_h >> srch_l;

//DynMat Name:Resmat
//Lines:mat_h-srch_h+1 rows:mat_l-srch_l+1
int **Resmat = new int *[mat_h - srch_h + 1];
for (int i = 0; i < mat_h - srch_h + 1; i++)
*(Resmat + i) = new int[mat_l - srch_l + 1]();
int *Resmat_cfg = new int(mat_h - srch_h + 1);
//End of Dynmat.

//construct 1st window
for (int i = 0; i < srch_h; i++)
for (int j = 0; j < srch_l; j++)
Resmat[0][0] += Pmat[i][j];

//construct horiz screener
for (int j = 1; j < mat_l - srch_l + 1; j++)
{
Resmat[0][j] = Resmat[0][j - 1];//get from left
for (int i = 0; i < srch_h; i++)
{
//column process
Resmat[0][j] -= Pmat[i][j - 1];
Resmat[0][j] += Pmat[i][srch_l + j - 1];
}
}

//screen
for (int i = 1; i < mat_h - srch_h + 1; i++)
{
//1st window
Resmat[i][0] = Resmat[i - 1][0];//get from upside
for (int j = 0; j < srch_l; j++)
{
//row process
Resmat[i][0] -= Pmat[i - 1][j];
Resmat[i][0] += Pmat[i + srch_h - 1][j];
}
//screener
for (int j = 1; j < mat_l - srch_l + 1; j++)
{
Resmat[i][j] = Resmat[i][j - 1];//left
for (int z = 0; z < srch_h; z++)
{
//column
Resmat[i][j] -= Pmat[i + z][j - 1];
Resmat[i][j] += Pmat[i + z][srch_l + j - 1];
}
}
}

//search
int max = 0;
for (int i = 0; i < mat_h - srch_h + 1; i++)
for (int j = 0; j < mat_l - srch_l + 1; j++)
max = max < Resmat[i][j] ? Resmat[i][j] : max;

cout << max;

//delete dynamic variable

//Release DynMat
//Name:Resmat
for (int i = 0; i < *Resmat_cfg; i++)
delete [] *(Resmat + i);
delete [] Resmat;delete Resmat_cfg;
//End of Release.

//Release DynMat
//Name:Pmat
for (int i = 0; i < *Pmat_cfg; i++)
delete [] *(Pmat + i);
delete [] Pmat;delete Pmat_cfg;
//End of Release.

return 0;
}

仍然是9/10超时。

后来看到,如果不使用原数组,而是建立一个前缀和数组(Cf. FineArtz, 2018),用来存储从(0,0)到这个点的所有数字和。这样计算取得了比较好的效果。

容易分析得到,动窗法适合一个固定大小的窗户的情形,但是在窗户的大小会发生变化时,需要重新对整个窗户进行计算。在此题中,窗户按不同大小有n^2个,这样做效率会比较低。

而采用前缀和的方式,在经过O(n)的one-pass计算后,每次取值都是O(1)的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

using namespace std;

int main()
{
int m, n;
cin >> m >> n;
//DynMat Name:Summat
//Lines:m+1 rows:n+1
int **Summat = new int *[m + 1];
for (int i = 0; i < m + 1; i++)
*(Summat + i) = new int[n + 1]();
int *Summat_cfg = new int(m + 1);
//End of Dynmat.
int getnum;
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
{
cin >> getnum;
Summat[i][j] = getnum + Summat[i - 1][j] + Summat[i][j - 1] - Summat[i - 1][j - 1];
}
int l, h;
cin >> l >> h;
int max = 0;
int total = 0;
for (int i = 0; i < m + 1 - l; i++)
for (int j = 0; j < n + 1 - h; j++)
{
total = (Summat[i + l][j + h] + Summat[i][j] - Summat[i + l][j] - Summat[i][j + h]);
max = total > max ? total : max;
}
cout << max;
//Release DynMat
//Name:Summat
for (int i = 0; i < *Summat_cfg; i++)
delete[] * (Summat + i);
delete[] Summat;
delete Summat_cfg;
//End of Release.
return 0;
}

这么久重新回来重写这篇文章,其实优化一个算法的核心就是要减少重复,拿空间换时间。(这个题目甚至没有使用更多的空间)有名的Strassen算法,仅仅是减少了一次计算,都能够带来很大的 理论上的收益。(虽然现实意义很小,还不如硬算)

Ads by Google

Read our privacy policy on how these personalized advertisements are delivered to you.

For your reading experience, we provide full-text RSS feeds. Although math formulas cannot be displayed well, the interface can be adjusted as you like and there are no ads.