# Source code for tryalgo.dyn_prog_tricks

```#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""\
Dynamic Programming speedup tricks

christoph dürr - jill-jênn vie - 2022
"""

import sys

[docs]
def dyn_prog_Monge(W):
""" Solves the following dynamic program for 0 <= i < j < n

C[i,i] = 0
C[i,j] = W[i,j] + min over i < k <= j of (C[i,k-1] + C[k,j])
K[i,j] = minimizer of above

:param W: matrix of dimension n times n
:assumes: W satisfies the Monge property (a.k.a. quadrangle inequality) and monotonicity in the lattice of intervals
:returns: C[0,n-1] and the matrix K with the minimizers
:complexity: O(n^2)
"""
n = len(W)
C = [[W[i][i] for j in range(n)] for i in range(n)] # initially C[i,i]=W[i][i]
K = [[j for j in range(n)] for i in range(n)] # initially K[i,i]=i

# recursion
for j_i in range(1, n): # difference between j and i
for i in range(n - j_i):
j = i + j_i
argmin = None
valmin = float('+inf')
for k in range(max(i + 1, K[i][j - 1]),  K[i + 1][j] + 1):
alt = C[i][k - 1] + C[k][j]
if alt < valmin:
valmin = alt
argmin = k
C[i][j] = W[i][j] + valmin
K[i][j] = argmin
return C[0][n-1], K

def _decode(i, j, R, level, current):
"""is used by decode_root_matrix_to_level for recursive decoding."""
if i >= j:
return # nothing to do
root = R[i][j]
level[root] = current
_decode(i, root-1, R, level, current + 1)
_decode(root, j, R, level, current + 1)

[docs]
def decode_root_matrix_to_level(R):
"""Decodes a binary search tree encoded in the root matrix R into a level array
:returns: the level array
:complexity: linear
"""
n = len(R)
level = [0] * n
_decode(0, n - 1, R, level, 0)
return level[1:]

[docs]
def opt_bin_search_tree2(success, failure):
""" Optimal binary search tree on elements from 1 to n

:param success: n+1 dimensional array with frequency of every element i.
success[0] is ignored
:param failure: n+1 dimensional array with frequency between the elements,
failure[i] is frequency of a query strictly between element i and i+1.
These arrays do not have to be normalized.
:returns:       The value of an optimal search tree and the matrix with the roots for each
subproblem, encoding the actual tree.
:complexity: O(n^2)
"""
n = len(failure)
N = range(n)
W = [[failure[i] for j in N] for i in N]
for i in N:
for j in range(i+1, n):
W[i][j] = W[i][j - 1] + failure[j] + success[j]
return dyn_prog_Monge(W)

[docs]
def opt_bin_search_tree1(freq):
""" Optimal binary search tree on elements from 0 to n-1

:param freq: n dimensional array with frequency of every element i.
:returns:    The value of an optimal search tree and the matrix with the roots for each
subproblem, encoding the actual tree.
:complexity: O(n^2)
"""
n = len(freq)
return opt_bin_search_tree2([0] + freq, [0] * (n + 1))

if  __name__ == "__main__":