//Dr. Steinberg
//COP3503 Computer Science 2
//MatrixChainMult.java using Dynamic Programming

import java.io.*; 
import java.util.*; 

public class MatrixChainMult
{ 
	
	//Recursive Solution (no memoization)
	public static int matrixChainOrder(int p[], int i, int j)
	{ 
		if (i == j)
            return 0;
 
        int min = Integer.MAX_VALUE;
 

        for(int k = i; k < j; k++) 
		{
            int count = matrixChainOrder(p, i, k) + matrixChainOrder(p, k + 1, j) + p[i - 1] * p[k] * p[j];
 
            if(count < min)
                min = count;
        }
 
        return min;
	} 
	
	//Recursive Solution with Memoization
	public static int matrixChainOrderMemo(int p[], int i, int j, int[][] memo) 
    { 
		if(i == j)  
		{ 
			return 0; 
		}
		
		if(memo[i][j] != -1)  
		{ 
			return memo[i][j]; 
		} 
		
		memo[i][j] = Integer.MAX_VALUE; 
		
		for(int k = i; k < j; k++)  
		{ 
			memo[i][j] = Math.min(memo[i][j], matrixChainOrderMemo(p, i, k, memo) + matrixChainOrderMemo(p, k + 1, j, memo) + p[i - 1] * p[k] * p[j]); 
		} 
		
		return memo[i][j]; 
         
    }
	
	//Dynamic Programming using Tabulation
	public static int matrixChainOrderTabulation(int p[], int n) 
    { 
        int m[][] = new int[n][n];
		int s[][] = new int[n][n];

        for(int i = 1; i < n; i++)
            m[i][i] = 0;
 
        for(int l = 2; l < n; l++) 
        {
            for(int i = 1; i < n - l + 1; i++) 
            {
                int j = i + l - 1;
				
                if (j == n)
                    continue;
				
                m[i][j] = Integer.MAX_VALUE;
				
				
				for(int k = i; k <= j - 1; k++) 
                {
                    int q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
					
                    if(q < m[i][j])
					{
                        m[i][j] = q;
						s[i][j] = k;
					}
                }
            }
        }
		
		
		System.out.println("Minimum number of multiplications (using tabulation) is " + m[1][n - 1]);
		
		System.out.println("Optimized Parenthesis Output");
		
		printParenthesis(1, n - 1, n, s);
        return m[1][n - 1];
    }
	
	
	static int matrix = 1;
	
	public static void printParenthesis(int i, int j, int n, int[][] s)
	{
 
        if(i == j) 
		{
            System.out.print(matrix++);
            return;
        }
        System.out.print("(");
        printParenthesis(i, s[i][j], n, s);
 
        printParenthesis(s[i][j] + 1, j, n, s);
        System.out.print(")");
    }
	
	

	public static void main(String[] args) 
	{ 
		int arr[] = new int[] {5, 10, 4, 15, 2, 20};
		int size = arr.length;
		
		System.out.println("Minimum number of multiplications (using recusion) is " + matrixChainOrder(arr, 1, size - 1));
		
		
		int[][] memo = new int[100][100];
		
		for(int[] row : memo) 
			Arrays.fill(row, -1); 
		
		System.out.println("Minimum number of multiplications (using memoization) is " + matrixChainOrderMemo(arr, 1, size - 1, memo));
		
		matrixChainOrderTabulation(arr, size);
		
		System.out.println();
	} 
} 
