给定n个元素的数组和q个范围查询(本文中的范围总和)且不进行更新,任务是以有效的时间和空间复杂性来回答这些查询。应用平方根分解后,范围查询的时间复杂度为O(√ n) 。通过对数组的较早分解的块进行平方根分解,可以将平方根因子减小为恒定的线性因子。
先决条件:莫氏算法|前缀数组
方法 :
当我们对给定数组应用平方根分解时,查询范围和需要O(√ n)时间。
在此,计算在考虑中的块(角块)之间的块总和,这需要进行O(√ n)迭代。
初始数组:
将数组分解为块:
并且起始块和结束块上的总和的计算时间都需要O(√ n)迭代。
这将使我们每个查询的时间复杂度为:
= O(√n) + O(√n) + O(√n)
= 3 * O(√n)
~O(√n)
在这里,我们可以巧妙地降低查询算法的运行时间复杂度,方法是计算逐块前缀总和,然后使用它来计算在所考虑的块之间的块中累积的总和。考虑下面的代码:
interblock_sum[x1][x2] = prefixData[x2 - 1] - prefixData[x1];
上表计算所需时间为:
= O(√n) * O(√n)
~O(n)
注意:由于块x1和x2可能携带部分数据,因此我们未考虑这些块的总和。
前缀数组:
假设我们要查询范围从4到11的总和,我们考虑块0和块2之间的总和(不包括块0和块1中包含的数据),可以使用绿色块中的总和来计算如上图所示。
块0和块2之间的总和= 42 – 12 = 30
为了计算黄色块中剩余的总和,请考虑分解级别2的前缀数组,然后再次重复该过程。
在这里,请注意,尽管我们的运行时与上一方法相似,但已大大降低了每个查询的时间复杂度:
我们新的时间复杂度可以计算为:
= O(√n) + O(1) + O(√n)
= 2 * O(√n)
~O(√n)
2级的平方根分解:
此外,我们对从先前分解保留的每个分解块再次应用平方根分解。现在,在这个级别上,每个块中大约有√ n个子块,这些子块在上一级别进行了分解。因此,我们只需要在这些块上运行一次范围查询两次,一次用于启动块,一次用于结束块。
2级分解所需的预计算时间:
1〜&sqrt; n级别的块数
级别2〜 √  n中的块数
一级分解块的二级分解运行时:
= O(√n)
所有块的2级分解的总体运行时间:
= O(√n) * O(√n)
~O(n)
现在,我们可以在O(√√ n)时间查询我们的2级分解块。
因此,我们将整体时间复杂度从O(√ n)降低到O(√√ n)
查询边缘块所花费的时间复杂度:
= O(√√n) + O(1) + O(√√n)
= 2 * O(√√n)
~O(√√n)
总时间复杂度可以计算为:
= O(√√n)+O(1)+O(√√n)
= 2 * O(√√n)
~O(√√n)
3级的平方根分解:
使用此方法,我们可以一次又一次地递归分解数组d次,以将时间复杂度降低到恒定的线性度。
O(d * n1/(2^d)) ~ O(k), as d increases this factor converges to a constant linear term
下面提供的代码表示三重平方根分解,其中d = 3:
O(q * d * n1/(2 ^ 3)) ~ O(q * k) ~ O(q)
[其中q代表范围查询数]
// CPP code for offline queries in
// approx constant time.
#include
using namespace std;
int n1;
// Structure to store decomposed data
typedef struct
{
vector data;
vector> rdata;
int blocks;
int blk_sz;
}sqrtD;
vector> Sq3;
vector Sq2;
sqrtD Sq1;
// Square root Decomposition of
// a given array
sqrtD decompose(vector arr)
{
sqrtD sq;
int n = arr.size();
int blk_idx = -1;
sq.blk_sz = sqrt(n);
sq.data.resize((n/sq.blk_sz) + 1, 0);
// Calculation of data in blocks
for (int i = 0; i < n; i++)
{
if (i % sq.blk_sz == 0)
{
blk_idx++;
}
sq.data[blk_idx] += arr[i];
}
int blocks = blk_idx + 1;
sq.blocks = blocks;
// Calculation of prefix data
int prefixData[blocks];
prefixData[0] = sq.data[0];
for(int i = 1; i < blocks; i++)
{
prefixData[i] =
prefixData[i - 1] + sq.data[i];
}
sq.rdata.resize(blocks + 1,
vector(blocks + 1));
// Calculation of data between blocks
for(int i = 0 ;i < blocks; i++)
{
for(int j = i + 1; j < blocks; j++)
{
sq.rdata[i][j] = sq.rdata[j][i] =
prefixData[j - 1] - prefixData[i];
}
}
return sq;
}
// Square root Decomposition at level3
vector> tripleDecompose(sqrtD sq1,
sqrtD sq2,vector &arr)
{
vector> sq(sq1.blocks,
vector(sq1.blocks));
int blk_idx1 = -1;
for(int i = 0; i < sq1.blocks; i++)
{
int blk_ldx1 = blk_idx1 + 1;
blk_idx1 = (i + 1) * sq1.blk_sz - 1;
blk_idx1 = min(blk_idx1,n1 - 1);
int blk_idx2 = blk_ldx1 - 1;
for(int j = 0; j < sq2.blocks; ++j)
{
int blk_ldx2 = blk_idx2 + 1;
blk_idx2 = blk_ldx1 + (j + 1) *
sq2.blk_sz - 1;
blk_idx2 = min(blk_idx2, blk_idx1);
vector ::iterator it1 =
arr.begin() + blk_ldx2;
vector ::iterator it2 =
arr.begin() + blk_idx2 + 1;
vector vec(it1, it2);
sq[i][j] = decompose(vec);
}
}
return sq;
}
// Square root Decomposition at level2
vector doubleDecompose(sqrtD sq1,
vector &arr)
{
vector sq(sq1.blocks);
int blk_idx = -1;
for(int i = 0; i < sq1.blocks; i++)
{
int blk_ldx = blk_idx + 1;
blk_idx = (i + 1) * sq1.blk_sz - 1;
blk_idx = min(blk_idx, n1 - 1);
vector ::iterator it1 =
arr.begin() + blk_ldx;
vector ::iterator it2 =
arr.begin() + blk_idx + 1;
vector vec(it1, it2);
sq[i] = decompose(vec);
}
return sq;
}
// Square root Decomposition at level1
void singleDecompose(vector &arr)
{
sqrtD sq1 = decompose(arr);
vector sq2(sq1.blocks);
sq2 = doubleDecompose(sq1, arr);
vector> sq3(sq1.blocks,
vector(sq2[0].blocks));
sq3 = tripleDecompose(sq1, sq2[0],arr);
// ASSIGNMENT TO GLOBAL VARIABLES
Sq1 = sq1;
Sq2.resize(sq1.blocks);
Sq2 = sq2;
Sq3.resize(sq1.blocks,
vector(sq2[0].blocks));
Sq3 = sq3;
}
// Function for query at level 3
int queryLevel3(int start,int end, int main_blk,
int sub_main_blk, vector &arr)
{
int blk_sz= Sq3[0][0].blk_sz;
// Element Indexing at level2 decomposition
int nstart = start - main_blk *
Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;
int nend = end - main_blk *
Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;
// Block indexing at level3 decomposition
int st_blk = nstart / blk_sz;
int en_blk = nend / blk_sz;
int answer =
Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk];
// If start and end point don't lie in same block
if(st_blk != en_blk)
{
int left = 0, en_idx = main_blk * Sq1.blk_sz +
sub_main_blk * Sq2[0].blk_sz +
(st_blk + 1) * blk_sz -1;
for(int i = start; i <= en_idx; i++)
{
left += arr[i];
}
int right = 0, st_idx = main_blk * Sq1.blk_sz +
sub_main_blk * Sq2[0].blk_sz +
(en_blk) * blk_sz;
for(int i = st_idx; i <= end; i++)
{
right += arr[i];
}
answer += left;
answer += right;
}
else
{
for(int i = start; i <= end; i++)
{
answer += arr[i];
}
}
return answer;
}
// Function for splitting query to level two
int queryLevel2(int start, int end, int main_blk,
vector &arr)
{
int blk_sz = Sq2[0].blk_sz;
// Element Indexing at level1 decomposition
int nstart = start - (main_blk * Sq1.blk_sz);
int nend = end - (main_blk * Sq1.blk_sz);
// Block indexing at level2 decomposition
int st_blk = nstart / blk_sz;
int en_blk = nend / blk_sz;
// Interblock data level2 decomposition
int answer = Sq2[main_blk].rdata[st_blk][en_blk];
if(st_blk == en_blk)
{
answer += queryLevel3(start, end, main_blk,
st_blk, arr);
}
else
{
answer += queryLevel3(start, (main_blk *
Sq1.blk_sz) + ((st_blk + 1) *
blk_sz) - 1, main_blk, st_blk, arr);
answer += queryLevel3((main_blk * Sq1.blk_sz) +
(en_blk * blk_sz), end, main_blk, en_blk, arr);
}
return answer;
}
// Function to return answer according to query
int Query(int start,int end,vector& arr)
{
int blk_sz = Sq1.blk_sz;
int st_blk = start / blk_sz;
int en_blk = end / blk_sz;
// Interblock data level1 decomposition
int answer = Sq1.rdata[st_blk][en_blk];
if(st_blk == en_blk)
{
answer += queryLevel2(start, end, st_blk, arr);
}
else
{
answer += queryLevel2(start, (st_blk + 1) *
blk_sz - 1, st_blk, arr);
answer += queryLevel2(en_blk * blk_sz, end,
en_blk, arr);
}
// returning final answer
return answer;
}
// Driver code
int main()
{
n1 = 16;
vector arr = {7, 2, 3, 0, 5, 10, 3, 12,
18, 1, 2, 3, 4, 5, 6, 7};
singleDecompose(arr);
int q = 5;
pair query[q] = {{6, 10}, {7, 12},
{4, 13}, {4, 11}, {12, 16}};
for(int i = 0; i < q; i++)
{
int a = query[i].first, b = query[i].second;
printf("%d\n", Query(a - 1, b - 1, arr));
}
return 0;
}
44
39
58
51
25
时间复杂度: O(q * d * n 1 /(2 ^ 3) )&ap; O(q * k)&ap; O(q)
辅助空间: O(k * n)&ap;上)
注意:本文仅解释将平方根分解为进一步分解的方法。