877. Головоломка умножения

 

В головоломку умножения играют с рядом карт, каждая из которых содержит одно положительное целое число. Во время хода игрок убирает одну карту из ряда и получает число очков, равное произведению числа на убранной карте и чисел на картах, лежащих непосредственно слева и справа от неё. Не разрешено убирать первую и последнюю карты ряда. После последнего хода в ряду остаётся только две карты.

Цель игры – убрать карты в таком порядке, чтобы минимизировать общее количество набранных очков.

Например, если карты содержат числа 10, 1, 50, 20 и 5, игрок может взять карту с числом 1, затем 20 и 50, получая очки

10 * 1 * 50 + 50 * 20 * 5 + 10 * 50 * 5 = 500 + 5000 + 2500 = 8000

Если бы он взял карты в обратном порядке, то есть 50, затем 20, затем 1, количество очков было бы таким:

1 * 50 * 20 + 1 * 20 * 5 + 10 * 1 * 5 = 1000 + 100 + 50 = 1150.

 

Вход. В первой строке находится количество карт n, во второй – n (3 ≤ n ≤ 100) чисел на картах. Все числа на картах целые от 1 до 100.

 

Выход. Вывести одно целое число – минимально возможное число очков.

  

Пример входа

Пример выхода

6

10 1 50 50 20 5

3650

 

 

РЕШЕНИЕ

динамическое программирование

 

Анализ алгоритма

Входную последовательность карт занесем в массив a[0, …, n – 1]. Пусть f(i, j) – наименьшее количество баллов, которое можно получить, удалив все карты строго внутри отрезка [ai, …, aj] (кроме крайних двух ai и aj, крайние карты по условию задачи удалять нельзя). Сохраним это значение в ячейке dp[i][j].

Тогда ответом на задачу будет f(0, n – 1).

Занесем в ячейки массива dp значения INF = ∞, инициализируем f(i, i) = dp[i][i] = 0, f(i, i + 1) = dp[i][i + 1] = 0.

Предположим, что при убирании чисел внутри отрезка [ai, …, aj] последним будет убрано ak (i < k < j). При этом оно в общую сумму принесет слагаемое ai ak aj. Но для того чтобы ak на последнем шаге выбора карты было соседним с ai и aj, необходимо перед этим убрать все карты внутри отрезков [ai, …, ak] и [ak, …, aj]. То есть

f(i, j) =

 

Например,

f(i, i + 2) = f(i, i + 1) + f(i + 1, i + 2) + ai * ai+1 * ai+2 = ai * ai+1 * ai+2

 

Пример

 

f(1, 4) = min( f(1, 3) + f(3, 4) + 1 * 50 * 20,

                           f(1, 2) + f(2, 4) + 1 * 50 * 20) =

= min( 0 + 50000 + 1000, 2500 + 0 + 1000) = 3500

 

 

Значения f(i, j) = dp[i][j] приведем ниже:

 

Ответ равен f(0, 5) = 3650.

 

Упражнение

Рассмотрим следующий пример. Пусть нам извесны такие даные.

·        Вычислите значние f(0, 5).

·        Заполните таблицу для массива (5, 3, 4, 1, 6, 2):

 

Реализация алгоритма

Объявим рабочие массивы.

 

#define MAX 110

#define INF 0x3F3F3F3F

int dp[MAX][MAX], a[MAX];

 

Функция f(i, j) возвращает наименьшее количество баллов, которое можно получить, удалив все карты строго внутри отрезка [ai, …, aj] (кроме крайних двух ai и aj, крайние карты по условию задачи удалять нельзя).

 

int f(int i, int j)

{

  if (dp[i][j] == INF)

    for (int k = i + 1; k < j; k++)

      dp[i][j] = min(dp[i][j], f(i,k) + f(k,j) + a[i] * a[k] * a[j]);

  return dp[i][j];

}

 

Основная часть программы. Читаем входные данные. Инициализируем массив dp.

 

scanf("%d",&n);

memset(dp,0x3F,sizeof(dp));

for(i = 0; i < n; i++)

{

  scanf("%d",&a[i]);

  dp[i][i] = 0;

  dp[i][i+1] = 0;

}

 

Вычисляем и выводим ответ.

 

printf("%d\n",f(0,n-1));

 

Java реализация

 

import java.util.*;

 

public class Main

{

  static int dp[][], a[];

 

  public static int f(int i, int j)

  {

    if (dp[i][j] == Integer.MAX_VALUE)

      for (int k = i + 1; k < j; k++)

        dp[i][j] = Math.min(dp[i][j], f(i,k) + f(k,j) + a[i] * a[k] * a[j]);

    return dp[i][j];

  }

 

  public static void main(String[] args)

  {

    Scanner con = new Scanner(System.in);

    int n = con.nextInt();

    dp = new int[n][n];

    for(int i = 0; i < n; i++)

    for(int j = 0; j < n; j++)

      dp[i][j] = Integer.MAX_VALUE;     

 

    a = new int[n];

    for(int i = 0; i < n; i++)

    {

      a[i] = con.nextInt();

      dp[i][i] = 0;

      if (i < n - 1) dp[i][i+1] = 0;

    }

   

    int res = f(0, n - 1);

    System.out.println(res);

    con.close();

  }

}