在某些范围更新后获取整数数组的最终状态的有效算法是什么?

What is the efficient algorithm to get the final state of an integer array after some range updates?

本文关键字:状态 算法 是什么 有效 数组 范围 更新 整数 获取      更新时间:2023-10-16

我给出了一个数组arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}.我必须做一些范围更新。在每次更新中,我将得到三个整数left, right, new_value。这意味着我必须将arr的所有元素从索引left更新到right(从 0 开始的索引)再到new_value。最后,我必须告诉这些更新后数组arr的最终状态是什么。

在本例中,假设有 2 个更新。第一次更新说将索引0...3更新为13,第二次更新说将索引更新2...60arr的最终状态是{13, 13, 0, 0, 0, 0, 0, 8, 9, 10}。 这是我尝试过的代码:

int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (size_t i = 1; i <= 2; i++)
{
int left, right, new_value;
cin >> left >> right >> new_value;
for (size_t j = left; j <= right; j++)
{
arr[j] = new_value;
}
}
for (size_t i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
}

但问题是数组的大小是n并且有q查询。那么我的方法的时间复杂度就O(n * q).我的问题是,什么是更好的方法?

您需要做的是创建一个中间数据结构,该结构进行范围更新的成本很低。

最简单的数据结构是树。 一个实现可以让树的每个节点包含以下字段:

left_index
right_index
left_subtree
right_subtree
is_constant
value

您可以及时O(n)填充它,方法是用相同的索引填充叶子,子树 null,is_constanttrue 和值,然后用is_constantfalse 填充所有上层。

每个更新查询仅涉及自上而下的遍历。 诀窍是,如果您在树中设置了更高的is_constant,则无需更新其下方的子树 - 它们都将被"屏蔽"。 因此,每次更新都是时间O(log(n))

从树复制回阵列又是一个O(n)操作。

树代码将适度棘手,但q查询的总时间为O(n) + O(q * log(n)) + O(n) = O(n + q * log(n))。 这是对O(q * n)的重大改进。


以下是树更新工作原理的概述。

所以我们有一棵树。 我们有三个价值观,left, right, value. 然后,更新通过以下 Pythonish 伪代码递归进行:

def update_tree (self, left, right, value):
if right < left:
return # empty interval
elif right < self.left_index:
return # No overlap
elif self.right_index < left:
return # No overlap
elif left <= self.left_index and self.right_index <= right:
# No need to update the subtree - this is our win.
self.is_constant = True
self.value = value
else:
# We need to only update part of this tree.
self.is_constant = False
self.left_subtree.update_tree(left, right, value)
self.right_subtree.update_tree(left, right, value)

有效执行此操作的最简单方法是维护一个有序集合,该集合仅存储索引 0 的值以及值与前一个索引不同的索引。

由于您使用的是C++,因此您可以将索引 -> 值映射放在std::map<int,int>中。

在每次更新时,您花费O(log n)时间来查找修改地图的位置(使用map.lower_bound),然后您将添加最多2个条目,并可能删除一些预先存在的条目或您之前添加的一些条目。

现有条目的总数为 <= n,您添加的条目总数为 <= 2q,因此您删除的条目总数为<= n + 2q

总复杂度为O(n + q * log n)

这似乎也是优先级队列的情况。将查询的每个左侧和右侧部分与相应的数组单元格相关联。

[(13,1), 2, (0,2), (-13,1), 5,
6, (-0,2), 8, 9, 10]
(If more than one fall on one cell,
aggregate them.)

现在,当我们从左到右遍历时,我们对一件事感兴趣:我们当前所处的更新间隔(如果有的话)最后在查询中提供?后一个"排名"是查询部分的优先级。

我们从 (13,1) 开始,将其放在优先级队列中并保持输出 13,直到达到 (0,2)。我们将 (0,2) 添加到优先级队列中,该队列具有更高的优先级。我们继续输出 0。我们到达 (-13,1),它告诉我们从优先级队列中删除 (13,1),并继续放置 0,直到 (-0,2) 调用删除 (0,2)。我们以 8,9,10 结束。

将区间代数应用于查询。 新查询优先于旧查询。 在你的发布情况下,你从零封开始。 第一个查询添加事务

0   3    13

第二个将此表更新为

0   1    13     // note the range change -- intervals may not overlap
2   6     0

大多数语言都有间隔模块,可以为您支持此类操作。 您的表已排序,因此对于新查询的每个端点,搜索不比O(log q)差 - 并且直接索引方法会将其降至O(n)。

在查询结束之前,请勿进行任何更新。 间隔过程为 O(q),更新为 O(n),因此整体复杂度为O(q+n)。