查找具有负值

时间:2017-06-08 17:56:49

标签: arrays algorithm tree bit segment

给定一系列正元素(基于1的索引),您必须处理两种类型的查询:

  1. (V)求1:V(包括两者)
  2. 范围内的数字之和
  3. (V,X)从1:V范围内的全部减去数字X并报告范围1:V中的最大索引i,使得该索引处的值为负,其中此查询的答案为0如果没有这样的索引。
  4. 我可以使用fenwick树或分段树进行第一次查询但是我如何支持第二次查询?我已经尝试了每个查询方法的O(n)时间,只检查范围1 ... V中的每个元素,但它超时。我需要在大小为10 ^ 5的数组上处理10 ^ 5个这样的查询。

2 个答案:

答案 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的最坏情况非常糟糕)。既然你说过这个时间,那么这是一个更复杂的方法。

  1. 创建一个AVL树或另一个支持插入和删除的自平衡二叉树。
  2. 创建包含属性valueindex的关键项。 value是商品的价值,而index是商品在平面数组中的位置。
  3. 根据index
  4. 创建链接到树节点的散列图
  5. 将项目添加到树中,使其平衡value,但根据index删除项目。
  6. 更新平面阵列后,请相应地更新树。
  7. 要回答查询2,请对树进行有序遍历,直到value-X>=0为止。返回错误的最后一个节点的index
  8. 树的插入和删除都在 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)。

编辑:更好的解释

首先构建一个分段树,使叶子将原始值存储在数组中。每个其他节点都使用两个值构建:totalsumminval。使用以下等式轻松构建:

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是查询的数量。