0

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

 2 years ago
source link: https://victrid.dev/2020/prefix-sum-simple/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Victrid's Personal Site
This website uses cookies to provide better contents. Check our privacy policy.

SJTUOJ 1002.二哥种花生

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

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

#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]

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

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

//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)的。

#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算法,仅仅是减少了一次计算,都能够带来很大的 理论上的收益。(虽然现实意义很小,还不如硬算)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK