我已经阅读了一些关于两个常见数据结构的教程,这些结构可以在O(lg N)中实现范围更新和查询:Segment tree和Binary Indexed Tree(BIT / Fenwick树)。
我发现的大多数例子都是关于某些关联和交换操作,如"范围内的整数和","范围内的XOR整数"等等。
我想知道这两个数据结构(或任何其他数据结构/算法,请提出)是否可以在O(lg N)中实现以下查询? (如果不是,O(sqrt N)怎么样)
给定一个整数A的数组,查询范围[l,r]
中的不同整数的数量 PS:假设可用整数的数量是~10 ^ 5,那么used[color] = true
或位掩码是不可能的
例如:A = [1,2,3,2,4,3,1],query([2,5])= 3,其中范围索引从0开始。
答案 0 :(得分:12)
是的,即使您应该在线回答查询,也可以在O(log n)中执行此操作。但是,这需要一些相当复杂的技术。
首先,让我们解决以下问题:给定一个数组,回答形式&#34的查询;索引[l,r]"中有多少个数< = x?这是通过一种类似于树的结构来完成的,有时也称为合并排序树。它基本上是一个分段树,其中每个节点存储一个已排序的子阵列。这种结构需要O(n log n)内存(因为有log n层,每个都需要存储n个数字)。它也是用O(n log n)构建的:你只需要自下而上,并为每个内部顶点合并其子项的排序列表。
这是一个例子。假设1 5 2 6 8 4 7 1
是原始数组。
|1 1 2 4 5 6 7 8|
|1 2 5 6|1 4 7 8|
|1 5|2 6|4 8|1 7|
|1|5|2|6|8|4|7|1|
现在,您可以在O(log ^ 2 n时间)内回答这些查询:只需对段树进行重新查询(遍历O(log n)节点)并进行二进制搜索以了解有多少数字< = x存在于该节点中(此处附加O(log n))。
这可以使用Fractional Cascading技术加速到O(log n),这基本上允许您不在每个节点中进行二进制搜索,而只在根中进行。然而,它很复杂,无法在帖子中描述。
现在我们回到原来的问题。假设你有一个数组a_1,...,a_n。构建另一个数组b_1,...,b_n,其中b_i =数组中下一次出现a_i的索引,如果是最后一次出现,则为∞。
示例(1索引):
a = 1 3 1 2 2 1 4 1
b = 3 ∞ 6 5 ∞ 8 ∞ ∞
现在让我们计算[l,r]中的数字。对于每个唯一的数字,我们将计算它在该段中的最后一次出现。使用b_i概念,您可以看到,当且仅当b_i > r
时,数字的出现才是最后的。所以问题归结为"有多少个> r在段[l,r]和#34;这可以简单地简化为我上面描述的内容。
希望它有所帮助。
答案 1 :(得分:2)
给定的问题也可以使用Mo(离线)算法(也称为平方根分解算法)来解决。
总体时间复杂度为O(N * SQRT(N))。
有关详细说明,请参阅mos-algorithm,它甚至可以通过此方法解决复杂性分析和SPOJ问题。
答案 2 :(得分:2)
有一种众所周知的离线方法可以解决此问题。如果您有n个大小数组并在每个查询上有q个查询,则需要知道该范围内的不同数的计数,那么您就可以用O(n log n + q log n)时间复杂度来解决整个问题。这类似于解决O(log n)时间中的每个查询。
让我们使用RSQ(范围和查询)技术解决问题。对于RSQ技术,可以使用段树或BIT。让我们讨论分段树技术。
要解决此问题,您需要离线技术和段树。现在,什么是离线技术?离线技术是在做离线的事情。解决问题的一种脱机方法示例是,首先输入所有查询,然后对它们进行重新排序,这是一种使您可以正确,轻松地回答它们并最终以给定的输入顺序输出答案的方法。
解决方案:
首先,接受测试用例的输入并将给定的n个数字存储在数组中。令数组名称为array []并接受输入q个查询并将其存储在向量v中,其中v的每个元素都包含三个字段l,r,idx。其中,l是查询的起点,r是查询的终点,idx是查询的数量。像这样的第n个查询。 现在根据查询的端点对向量v进行排序。 让我们有一个段树,它可以存储至少10 ^ 5个元素的信息。并且我们还有一个叫last [100005]的区域。它将数字的最后位置存储在array []中。
最初,树的所有元素为零,最后一个树的所有元素为-1。 现在在array []上运行一个循环。现在进入循环,您必须检查array []的每个索引的内容。
last [array [i]]是否为-1?如果为-1,则编写last [array [i]] = i并调用update()函数,该函数将在段树的last [array [i]]位上添加+1。 如果last [array [i]]不为-1,则调用段树的update()函数,该函数将在段树的last [array [i]]位中减去1或加-1。现在,您需要将当前位置存储为将来的最后位置。因此您需要编写last [array [i]] = i并调用update()函数,该函数将在段树的last [array [i]]位上添加+1。
现在,您必须检查查询是否在当前索引中完成。那就是if(v [current] .r == i)。如果为true,则调用段树的query()函数,该函数将返回范围v [current] .l与之和的总和,并将结果存储在v [current] .idx ^的索引中answer []数组。您还需要将current的值增加1。 6.现在,按指定的输入顺序打印包含最终答案的answer []数组。
算法的复杂度为O(n log n)。答案 3 :(得分:1)
kd-trees在O(logn)中提供范围查询,其中n是点数。
如果您想要比kd树更快的查询,并且您愿意支付内存费用,那么Range trees是您的朋友,提供以下查询:
O(log d n + k)
其中n是树中存储的点数,d是每个点的维数,k是给定查询报告的点数。
Bentley是这个领域的重要名称。 :)
答案 4 :(得分:0)
如果您愿意离线回答查询,那么普通的旧“段树/ BIT”仍然可以提供帮助。
对于输入数组中从左到右的每个值:
对于当前元素,如果以前已经看到过,则递减1 in
段树在其先前位置。
通过查询范围为[l,r == i]的和来回答以当前索引i结尾的查询。
简而言之,该方法是保持标记向右的索引,每个元素的最新出现以及将先前出现的位置设置回0。范围的总和将给出唯一元素的计数。
总体时间复杂度将为nLogn。