给定一系列正元素(基于1的索引),您必须处理两种类型的查询:
我可以使用fenwick树或分段树进行第一次查询但是我如何支持第二次查询?我已经尝试了每个查询方法的O(n)时间,只检查范围1 ... V中的每个元素,但它超时。我需要在大小为10 ^ 5的数组上处理10 ^ 5个这样的查询。
答案 0 :(得分:0)
最直接的方法是通过简单地搜索数组来找到 O(n)时间中的元素,如下所示:
arr = [0,5,2,3,10]
largest_i = -1
X = 7
for i in range(len(arr)):
if arr[i]-X<0:
largest_i = i
largest_i+1 #Your answer (shifting from a 0-based to a 1-based index)
鉴于数组的小尺寸和时间限制,这应该适用于任何编译和解释最多的语言。
修改强>
我的立场得到了纠正(而且应该很明显,10 ^ 10的最坏情况非常糟糕)。既然你说过这个时间,那么这是一个更复杂的方法。
value
和index
的关键项。 value
是商品的价值,而index
是商品在平面数组中的位置。index
value
,但根据index
删除项目。value-X>=0
为止。返回错误的最后一个节点的index
。树的插入和删除都在 O(log n)中,而有序遍历的最坏情况是 O(n),但是保证只检查可能是否定的元素。
可能的实现如下:
#include <map>
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cstdlib>
#include <cassert>
class MagicArray {
private:
typedef std::multimap<int, int> arr_idx_t;
std::vector<int> arr; //Array of possibly negative integers
arr_idx_t arr_sorted; //Self-balancing tree of (Integer, Index) pairs
std::unordered_map<int, arr_idx_t::iterator> arr_idx; //Hash table linking Index to a (Integer, Index)
void indexElement(const int idx){
auto ret = arr_sorted.emplace(arr.at(idx),idx);
arr_idx[idx] = ret;
}
public:
void insert(const int i){
arr.emplace_back(i);
const auto idx = arr.size()-1; //Index of most recently inserted element
indexElement(idx);
}
void alter(const int idx, const int newval){
arr.at(idx) = newval;
arr_sorted.erase(arr_idx[idx]); //Remove old value from tree
indexElement(idx);
}
int findMatch(const int X){
//The next two lines reduce run-time from 3s to 0.031s
if(arr_sorted.rbegin()->first-X<0) //Even largest element is less than zero
return arr.size();
int foundi = -1;
for(const auto &kv: arr_sorted){
if(kv.first-X<0){
foundi = std::max(foundi,kv.second);
} else {
break;
}
}
return foundi+1;
}
};
int main(){
assert(RAND_MAX>10000000); //Otherwise code below will not work
MagicArray ma;
for(unsigned int i=0;i<10000;i++)
ma.insert(rand()%10000);
for(unsigned int i=0;i<10000;i++){
ma.alter(rand()%10000,rand()%10000);
ma.findMatch(rand()%1000000);
}
}
如果省略findMatch
的前两个代码行,我的英特尔(R)酷睿(TM)i5 CPU M480(2.67GHz)和英特尔(R)Xeon(R)上的2.359秒需要3秒超级计算机上的CPU E5-2680v3 @2.50GHz。
如果您包含findMatch
的前两个代码行,则代码在两台计算机上都小于&lt; 0.035秒。
这就是考虑值范围非常重要的原因。你说数组包含[0,10⁵]范围内的值,而X在[0,10⁷]范围内,这意味着99%的X
值将大于数组中的任何值因此,答案只是数组的大小。
所以诀窍是使用便宜的支票来查看我们是否只是简单地知道答案,如果没有,那么就可以执行更昂贵的搜索。
答案 1 :(得分:0)
使用分段树,使每个节点在其范围内存储最小值,并在其范围内存储元素的总和。第一次查询可以在logn时间复杂度中直接完成。对于第二个查询,首先从范围中的每个元素中减去给定值(再次登录),然后查询最右边的值小于0(也是logn)。
编辑:更好的解释
首先构建一个分段树,使叶子将原始值存储在数组中。每个其他节点都使用两个值构建:totalsum
和minval
。使用以下等式轻松构建:
segment_tree[id].minval = min(segment_tree[id*2].minval, segment_tree[id*2+1].minval)
segment_tree[id].totalsum = segment_tree[id*2].totalsum + segment_tree[id*2+1].totalsum
构建需要O(n)
。
查询A:在某个范围内查找总和很简单,只需查找与查询范围相关的最顶层范围并将其添加即可。每个查询的时间O(logn)
。
查询B:将此查询分为两个操作:
A)从范围中减去X:假设你从某个范围[a,b]中减去X.因此,[a,b]的总和变为old_totalsum[a,b] - (b+1-a)*X
,新的最小值变为old_minval[a,b] - X
。关键是你只在查询范围之下的段树的最顶层范围内再次执行它,这样操作只需要logn
复杂度。这种技术稍微多一点,如果你不熟悉它,你应该在线阅读它(它被称为懒惰传播)。
B)检查最右边的索引,其值为&lt; 0:在段树的根上开始查询。是右边的最小值&lt; 0?然后去那里。否则是左撇子的最小值&lt; 0?然后去左边的孩子。如果孩子有minval&gt; 0,返回-1。到达叶子后,只需返回叶子对应的索引即可。所以你再次沿着树的高度遍历O(logn)
。
因此,程序的总复杂程度为O(n + Q.logn)
,其中Q是查询的数量。