动态编程证明LISA

时间:2018-11-19 15:47:35

标签: algorithm dynamic-programming

我正在尝试解决https://www.spoj.com/problems/LISA/

在问题中,给出仅具有*和+的表达式。放置支架最大值需要输出。

喜欢

 1+2*3+4*5 = (1+2)*(3+4)*5 = 105

 2*0*3+7+1*0*3 = 2*((0*3+7+1*0)*3) = 42

我遇到的2D动态问题解决方法的实现如下。取每个数字是矩阵的行和列,并采用从下到上的方法。

f(ci,cj) = Max( f(i,j-1) operator c(i,j) , f( i+1,j-1) operator c(i,i) )

Like 3,5 =  Max of [  (3,4) * (5,5) or (3,3)+(4,5) ]
         =  Max of [   7*5  or 3+20 ] 
         =  Max of [ 35,23 ] = 35

我无法证明我得到的结果是最大且正确的。如何证明以下解决方案是最大和最优的?

-----------------------------------
      C1   C2   C3   C4    C5
C1     1    3    9   13    105
C2          2    6   14    70
C3               3   7     35
C4                   4     20
C5                         5

2 个答案:

答案 0 :(得分:1)

此问题可以归类为带有记忆的分而治之问题。

假设您的字符串是s = s1 op1 s2 op2 s3 op3 s4

现在您可以在每个s处将op分区

假设您将s的{​​{1}}分区为两个字符串:

左字符串:op1

右字符串:s1

让我们说,左字符串可获取的最小值,最大值为s2 op2 s3 op3 s4

,右字符串为min1, max1

您可能会认为min2, max2是最小值,而min1 op1 min2是最大值

但尚未完成。

您需要对每个max1 op1 max2执行此操作,并累积opmin的值。为什么?因为max上的分区可能不是最佳的。

然后从所有这些分区中选择op1min(mins)

您可以通过记住Java中的结果来递归地实现这一点,例如:

max(maxs)

答案 1 :(得分:1)

这是我的实现:(假定查询格式正确)

class LISASolver:
    def solve(self, query):
        """
        takes a query, a string with numbers and +, *,
        returns a tuple of min, max
        """
        numbers, operators = self.parse_inputs(query)
        n = len(numbers)
        self.numbers = numbers
        self.operators = operators
        self.memo = {}
        out = self.dp(0, len(numbers) - 1)
        return out

    def dp(self, i, j):
        if i == j:
            n = self.numbers[i]
            return n, n

        if (i, j) in self.memo:
            return self.memo[(i, j)]

        curmins = []
        curmaxs = []
        for k in range(i, j):
            o = lambda a, b: (a * b) if self.operators[k] == '*' else (a + b)
            leftmin, leftmax = self.dp(i, k)
            rightmin, rightmax = self.dp(k + 1, j)
            curmins.append(o(leftmin, rightmin))
            curmaxs.append(o(leftmax, rightmax))
        self.memo[(i, j)] = (min(curmins), max(curmaxs))

        return self.memo[(i, j)]

    def parse_inputs(self, query):
        numbers = []
        operators = []
        current_number = []

        for c in query:
            if c.isdigit():
                current_number.append(c)
            else:
                numbers.append(
                    int(''.join(current_number))
                )
                current_number = []
                operators.append(c)

        numbers.append(
            int(''.join(current_number))
        )
        return numbers, operators

s = LISASolver()
query = '1+2*3+4*5'
print(s.solve(query))
>>> 27, 105
query = '2*0*3+7+1*0*3'
print(s.solve(query))
>>> 0, 42

子问题是从第i个数字到第j个数字的最优最小和最小结果。通过在每个子阵列上计算结果的最小值和最大值,然后应用递归关系,可以确保最佳性。由于存在O(n ^ 2)个子问题,每个子问题都占用O(n),因此时间复杂度为O(n ^ 3)。

编辑:

说明:

对于动态编程,至关重要的是识别什么是子问题以及子问题与其他子问题的关系。对于说n个数字(因此称为n - 1运算符)的问题,子问题是:通过将i之间的数字和运算符组合在一起,可以得到的最小/最大值是多少?第j号(含)。

基本情况为i = j,我们只有一个数字,最小值/最大值本身。

对于任何j > i,此问题的子问题是k的范围是ij - 1左部分的最小值和最大值(i的子问题和k作为两个端点)和右侧部分(子问题中,k + 1j作为两个端点)。对于每个k,我们实际上要做的是将第k个运算符用作最后一个运算符,因此,每个k所能获得的最小值是left({{1 }})最小的权利(类似的是最大的权利)。 (请注意,运算符是operator*,它们是单调的,因此我们将最小值与最小值相结合,将最大值与最大值相结合。对于更常见的问题,例如+-,我们可能需要考虑很多情况,但基本结构应该相同)。因此,递归关系很简单

/

minimum(i, j) = min(minimum(i, k) [operator] minimum(k + 1, j) for each k)

我们必须一次解决每个子问题(总共(same for max)个)并将其缓存(或像人们所说的那样记住),每个子问题都需要一个O(n^2)循环才能解决。因此,时间复杂度为O(n)

对动态编程的更深入的了解确实可以帮助您解决所有类似的问题。如果您不确定在这种情况下会发生什么,我建议您阅读一些相关内容。