想象一个大小 n 的数组 A ,其中每个数组元素包含两个正整数 a i 和的 b'子> I 子>
有Q查询,其中每个查询可以是以下两种类型之一:
1)给定正整数 x ,找到 max(a i x + b i )所有我从 1 到 n
2)为某些 i a i 和 b i 的值>
查询的数量可能很大,因此天真O(Q * n)
算法不够。此外,x可以大到10 9 ,目标表达式的值可以大到10 18 。
可以使用某些段树的变体来解决这个问题吗?如果有类似的问题,请指出我。另外,你会如何解决这个问题呢?我不是在寻找代码,只是寻找逻辑的一些提示/指针。
修改:您可以假设查询类型1中的 x 值不会减少。
编辑2 :您可以假设在更新中, a 的值只会增加。
编辑3 :我找到了问题here的答案。感谢@Mikhail指出信封这个词。使用这个词太多次都有帮助; - )
答案 0 :(得分:3)
首先订购x值。 根据x0(最低x)处的值对线(ax + b是线)的值进行排序。 对于每对连续的线,找出它们的交叉点(更准确地说是交叉点的x坐标)。如果x坐标低于x0,则可以忽略该对。对于其他对,保留优先级队列以找到下一个交叉点。
现在,对于下一个交叉点之前的所有输入x,您输出当前的前导线。由于没有交叉点,订单不会改变,所以它们都是相同的线。到达交叉点时,意味着您的两条线交叉。因此,在排序列表中交换它们,并再次添加与新邻居的交叉点。
重复直到解决了所有输入案例。 如果交点不足,则意味着不会有任何其他订单更改,因此输出所有其他输入的最大值。
复杂度很可能是初始排序的结果,如果行数相对较小或者你必须做的更新次数是N ^ 2/2(每行与每一行相交),O(QlogQ + N ^ 2 * logN)
答案 1 :(得分:3)
@ Sorin的想法是正确的,但找到交叉点是低效的。你想要的是构建所有行的上包络。此信封将包含不超过N-1
个点(每个行在信封中的贡献不会超过一次)。因此,扫描其已排序的点将花费O(N+Q)
时间。
现在,我们将在O(NlogN)
时间内构建信封。首先,使用升序a
对所有行进行排序。这将需要O(NlogN)
。让我们假设为了简单起见a
的所有值都不同,这不会改变主要思想。
请注意,行0
和N-1
将形成信封的左右斜率。因为接近无穷大,常数b
不会发生变化。让我们从行0
开始,作为信封的第一部分。现在,我们将按升序a
的顺序浏览所有其他行,更新每一步的包络。我还要多次重复“信封”?嗯...
信封的更新(该死的!)与Graham scan的工作方式非常相似。在第一步,我们将行0
放在堆栈中。在每一步中,我们考虑下一行并丢弃堆栈顶部的所有行,直到新行适合。见图:
这里我们添加行i+1
。为此,我们需要丢弃行i
和i-1
。由于每行都添加到此堆栈并从中删除不超过一次,因此整个扫描仅需O(N)
。如您所见,排序一直都在进行。
想知道如何确定下一行是否适合当前的堆栈?比较i+1
行与i
之上的行i
的交叉点以及i-1
上的O(N)
。
现在我们有了信封(我发誓,这是最后一次),其余的很容易。我们有解决方案在O(Q + NlogN)工作。 HTH。
顺便提一下,有人指出这个算法很受欢迎,被称为Convex hull trick。
UPD 哇,我忘记了我们还需要不时更新信封:)这使问题复杂化。需要考虑一下。
无论如何,与处理O(1)
中类型1和O(1)
中类型2的查询的天真方法相比,这提供了相反的结果:类型1的O(N)
,{类型2 {1}}。