【问题标题】:Maximize A[i]*B[i] + A[i]*B[j] + A[j]*B[j], i != j, given two unordered lists of positive integers给定两个正整数的无序列表,最大化 A[i]*B[i] + A[i]*B[j] + A[j]*B[j], i != j
【发布时间】:2021-11-01 17:50:12
【问题描述】:

你能帮我解决一下算法吗: 给定 2 个大小相等的数组 a[]b[],其中整数大于或等于 1。

找到不相等的索引 ij (i != j) 使得值 -

max(a[i]*b[i] + a[i] * b[j] + a[j] * b[j], a[i]*b[i] + a[j] * b[i] + a[j] * b[j])

将是最大值。

例子:

a = [1, 9, 6, 6]b = [9, 1, 6, 6].

最大值将在 i = 2 和 j = 3(从零开始的索引): a[2]*b[2] + a[2]*b[3] + a[3] * b[3] = 6*6+6*6+6*6 = 108

有没有办法在小于二次的时间内找到 i 和 j? 同样的问题是在小于二次的时间内找到目标函数值?

谢谢!

【问题讨论】:

  • 您提到了 i != j,然后在等式中您使用了 a[i] *b[i],在这种情况下 a 和 b 具有相同的索引。
  • @DataMoguls 是的。我们在索引 i 处从 a 和 b 中获取一对值,在索引 j 处获取另一个值。你能澄清什么让你感到困惑吗?
  • 数组是否只包含正数?
  • @MrSmith42 是的,只有正数和整数。将添加到描述中。
  • @LearningMathematics。例如:a = [1, 9, 6, 6] 和 b = [9, 1, 6, 6]。您是否建议取 i = 0 和 j = 1,在这种情况下,我们将有 max(9+9+81, 9 + 9 + 1) = 99,但最大值将是 i = 2,并且 j = 3,max( 6*6 + 6*6 + 6*6, 6*6 + 6*6 + 6*6) = 108。

标签: arrays algorithm maximize


【解决方案1】:

这是我实现David Eisenstat's idea 的尝试。我认为i != j 限制使这更加复杂,但无论哪种方式,我都欢迎有关改进代码的建议。最后有一个针对蛮力的测试。

线 A[i]*x + A[i]*B[i] 的上包络构造依赖于 Andrew 的单调链凸包算法,该算法应用于从线转换的对偶点。

Python 代码:

# Upper envelope of lines in the plane

from fractions import Fraction
import collections

def get_dual_point(line):
  return (line[0], -line[1])

def get_primal_point(dual_line):
  return (dual_line[0], -dual_line[1])

def get_line_from_two_points(p1, p2):
  if p1[0] == p2[0]:
    return (float('inf'), 0)

  m = Fraction(p1[1] - p2[1], p1[0] - p2[0])
  b = -m * p1[0] + p1[1]

  return (m, b)

def cross_product(o, a, b):
  return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])


# lower_hull has the structure [(dual_point, original_line, leftmost_x, index)]
def get_segment_idx(lower_hull, x):
  lo = 0
  hi = len(lower_hull) - 1

  # Find the index of the first
  # x coordinate greater than x
  while lo < hi:
    mid = lo + (hi - lo + 1) // 2

    if lower_hull[mid][2] <= x:
      lo = mid
    else:
      hi = mid - 1

  return lo


# Assumes we add points in order of increasing x-coordinates
def add_right_to_lower_hull(lower_hull, point):
  while len(lower_hull) > 0 and lower_hull[-1][0][0] == point[0][0] and lower_hull[-1][0][1] > point[0][1]:
    lower_hull.pop()
  while len(lower_hull) > 1 and cross_product(lower_hull[-2][0], lower_hull[-1][0], point[0]) <= 0:
    lower_hull.pop()
  if not lower_hull or lower_hull[-1][0][0] != point[0][0]:
    lower_hull.append(point)
  # Each segment of the lower hull
  # in the dual plane is a line intersection
  # in the primal plane.
  if len(lower_hull) == 1:
    lower_hull[0][2] = -float('inf')
  else:
    line = get_line_from_two_points(lower_hull[-1][0], lower_hull[-2][0])
    lower_hull[-1][2] = get_primal_point(line)[0]

  return lower_hull


# Assumes we add points in order of decreasing x-coordinates
def add_left_to_lower_hull(lower_hull, point):
  while len(lower_hull) > 0 and lower_hull[0][0][0] == point[0][0] and lower_hull[0][0][1] > point[0][1]:
    lower_hull.popleft()
  while len(lower_hull) > 1 and cross_product(lower_hull[1][0], lower_hull[0][0], point[0]) >= 0:
    lower_hull.popleft()
  if not lower_hull or lower_hull[0][0][0] != point[0][0]:
    lower_hull.appendleft(point)
  # Each segment of the lower hull
  # in the dual plane is a line intersection
  # in the primal plane.
  if len(lower_hull) == 1:
    lower_hull[0][2] = -float('inf')
  else:
    line = get_line_from_two_points(lower_hull[0][0], lower_hull[1][0])
    lower_hull[1][2] = get_primal_point(line)[0]

  return lower_hull


# Maximise A[i] * B[i] + A[i] * B[j] + A[j] * B[j]
def f(A, B):
  debug = False

  if debug:
    print("A: %s" % A)
    print("B: %s" % B)

  best = -float('inf')
  best_idxs = ()

  indexed_lines = [((A[i], A[i] * B[i]), i) for i in range(len(A))]

  # Convert to points in the dual plane
  # [dual_point, original_line, leftmost x coordinate added later, original index]
  dual_points = [[get_dual_point(line), line, None, i] for line, i in indexed_lines]

  # Sort points by x coordinate ascending
  sorted_points = sorted(dual_points, key=lambda x: x[0][0])

  if debug:
    print("sorted points")
    print(sorted_points)

  # Build lower hull, left to right
  lower_hull = []

  add_right_to_lower_hull(lower_hull, sorted_points[0])

  for i in range (1, len(sorted_points)):
    # Query the point before inserting it
    # because of the stipulation that i != j
    idx = sorted_points[i][3]
    segment_idx = get_segment_idx(lower_hull, B[idx])
    m, b = lower_hull[segment_idx][1]
    j = lower_hull[segment_idx][3]
    candidate = m * B[idx] + b + A[idx] * B[idx]

    if debug:
      print("segment: %s, idx: %s, B[idx]: %s" % (segment_idx, idx, B[idx]))

    if candidate > best:
      best = candidate
      best_idxs = (idx, j)

    add_right_to_lower_hull(lower_hull, sorted_points[i])
  
  if debug:
    print("lower hull")
    print(lower_hull)

  # Build lower hull, right to left
  lower_hull = collections.deque()

  lower_hull.append(sorted_points[len(sorted_points) - 1])

  for i in range (len(sorted_points) - 2, -1, -1):
    # Query the point before inserting it
    # because of the stipulation that i != j
    idx = sorted_points[i][3]
    segment_idx = get_segment_idx(lower_hull, B[idx])
    m, b = lower_hull[segment_idx][1]
    j = lower_hull[segment_idx][3]
    candidate = m * B[idx] + b + A[idx] * B[idx]

    if debug:
      print("segment: %s, idx: %s, B[idx]: %s" % (segment_idx, idx, B[idx]))

    if candidate > best:
      best = candidate
      best_idxs = (idx, j)

    add_left_to_lower_hull(lower_hull, sorted_points[i])

  if debug:
    print("lower hull")
    print(lower_hull)

  return best, best_idxs


#A = [1, 9, 6, 6]
#B = [9, 1, 6, 6]

#print("")
#print(f(A, B))


# Test

import random

def brute_force(A, B):
  best = -float('inf')
  best_idxs = ()

  for i in range(len(A)):
    for j in range(len(B)):
      if i != j:
        candidate = A[i] * B[i] + A[i] * B[j] + A[j] * B[j]
        if candidate > best:
          best = candidate
          best_idxs = (i, j)

  return best, best_idxs


num_tests = 500
n = 20
m = 1000

for _ in range(num_tests):
  A = [random.randint(1, m) for i in range(n)]
  B = [random.randint(1, m) for i in range(n)]

  _f = f(A, B)
  _brute = brute_force(A, B)

  if _f[0] != _brute[0]:
    print("Mismatch")
    print(A)
    print(B)
    print(_f, _brute)

print("Done testing.")

【讨论】:

  • 看起来,我不明白你如何将线相交任务的凸包转换为点的凸包。您使用双平面,但我不知道这一点。谢谢,我需要一些时间来理解。
  • @Sergey 转换出现在我阅读的一些文本中。我现在找不到它,但我在谷歌上搜索凸包线,然后是增量包,然后是上包络,我不记得确切的搜索词。这是另一篇可能有帮助的文章:citeseerx.ist.psu.edu/viewdoc/…
【解决方案2】:

是的,有一个 O(n log n) 时间的算法。

如果我们看一下函数 f(x) = max {a[i] b[i] + a[i] x |一世}, 它描绘了一个较低(凸)的船体。评价基本就够了 f(b[j]) + a[j] b[j] 对于每个 j,其中一个关键点是 我们需要 i ≠ j。

为了解决这个问题,我们使用增量船体算法 摊销的 O(log n) 时间插入。规范算法保持 船体按排序顺序,允许 O(log n) 时间查询。从...开始 一个空外壳,对于每个索引 i,我们查询 i 然后插入 i。做这个 正序一次,逆序一次,取最大值。

【讨论】:

  • 不确定如何查询 O(log n) 时间?为了评估 max{...},我们需要进行 O(n) 计算并对每个 i 进行计算。我对吗?我不知道我们如何改进它。
  • @Sergey 实现增量 hull 的最简单方法是对 a 和 b 应用相同的随机排列,并在不平衡的二叉搜索树中保持当前的 hull 边缘,由于洗牌。搜索树是从左到右排序的,所以查询基本上是一个常规的 BST 查找。计算几何文本中的更多详细信息。
  • @David_Eisenstat,对不起,我明白我们如何应用凸包算法,我不明白为什么会这样(n log n)。我的推理如下:1)Convex Hull 的速度是 O(k log(k)) 其中 k - 点数。 2)在任务的情况下,总点数:k = O(n^2) 在最坏的情况下,这等于线交叉点的数量(更精确的最坏情况大约是 n*(n-1) / 2)结果: O(n^2 log(n)) 在最坏的情况下。我错过了什么吗?
  • @Sergey 每个a[i] b[i] + a[i] x 是平面上的一条线。如果我们在坐标轴上画几条随机直线,很容易看出每个x 的最佳选择是当前位于顶部的直线。这意味着我们想要跟踪凸包的线段。我们希望安排构成船体的相关段,以便我们可以有效地查询xb[j]
  • 添加了尝试实现此功能的 Python 代码。它似乎根据最后的蛮力测试起作用。 i != j 让实现和调试有点痛苦。
猜你喜欢
  • 2021-04-09
  • 1970-01-01
  • 2017-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-01
  • 2019-04-19
相关资源
最近更新 更多