假设我们有一个n个元素的数组(未排序)。给定一个带有两个参数i和j的查询,其中i和j是数组的索引,我想返回值x st的值。 x在范围A[i],A[j]
(不包括)范围内,x本身在索引范围i<indexof(x)<j
内。
例如,数组是[3,6,7,1,2,4,9,1]
i=1 j=7
A[i]=3 A[j]=9
所以从索引1到7的范围3,9内的值是
6,7,4
导致3个值。
我肯定需要做一些预处理,以便我可以在O(logn)时间内回答查询。 我尝试使用Fenwick树来解决这个问题,但我想它需要进行一些修改,而且我不需要对数组进行任何更新,只需回答查询。
编辑:预计算O(n ^ 2)和O(1)查询对我来说不是一个有效选项
答案 0 :(得分:4)
可以通过使用与合并排序相关的分段树来解决此问题。在对应于范围[l,r]的每个节点处,存储在该节点中的数组将是A [l ... r]的排序数组。我们可以在O(n log n)时间内预处理这个,并且空间复杂度也将是O(n log n),因为每个元素在树的每个高度只出现一次,等于O(log n)。 / p>
构建此树的简单方法是使用O(n log n)算法对每个节点处的数组进行排序,该算法总时间复杂度为O(n log ^ 2 n)。但是我已经提到过这个过程看起来像是合并排序,所以我们可以使用相同的过程来获得O(n log n)构建时间的时间复杂度。
例如,让我们考虑示例数组的前四个元素[3,6,7,1]。我描述的树将如下所示:
[1,3,6,7]
/ \
[3,6] [1,7]
/ \ / \
[3] [6] [7] [1]
现在,如果您在相应节点上二进制搜索元素,则可以在O(log ^ 2 n)时间内完成查询。
建造时间:O(n log n)
查询时间:O(log ^ 2 n)
编辑(用C ++构建树的代码,查询留作练习):
#include <vector>
using namespace std;
const int MAX_N = 10000;
int A[MAX_N], N; // the array of the integers
vector<int> T[MAX_N * 4];
void merge(vector<int>& C, vector<int>& A, vector<int>& B){
int i = 0, j = 0, n = A.size(), m = B.size();
C.assign(n + m, 0);
while(i < n || j < m){
if(i == n) C[i + j] = B[j], j++;
else if(j == m) C[i + j] = A[i], i++;
else {
if(A[i] <= B[j]) {
C[i + j] = A[i];
i++;
} else {
C[i + j] = B[j];
j ++;
}
}
}
}
void build(int n, int L, int R){
if(L == R) T[n] = vector<int>(1, A[L]);
else {
build(n * 2, L, (L + R) / 2);
build(n * 2 + 1, (L + R) / 2 + 1, R);
merge(T[n], T[n * 2], T[n * 2 + 1]);
}
}
int main(){
N = 4;
A[0] = 3, A[1] = 6, A[2] = 7, A[3] = 1;
build(1, 0, N - 1);
return 0;
}
答案 1 :(得分:0)
这可以通过Fenwick的树来解决。首先让我们假设所有整数都小于10 ^ 6。
阻止您直接使用Fenwick的问题是:如果您在完成Fenwick的树设置后查询,查询(a[i]
,a[j]
)将涉及一些要计算a[k]
的数字k > j
。解决此问题的方法是根据右侧对查询进行排序,并在完成j
的Fenwick更新后立即使用正确的索引a[j]
完成所有查询。这将确保来自后期索引的数字不会影响以前的计数。
如果所有数字都在整数范围内,请在启动上述例程之前将它们映射到[1..N]
,其中N
是数组的大小。
整体复杂度为O(Q log Q + Q log N)
,其中Q
是查询数量,N
是数组的大小。