Задача о расстановке знаков в выражении — различия между версиями
Ruslan02 (обсуждение | вклад) (→Реализация) |
м (rollbackEdits.php mass rollback) |
||
(не показано 7 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
{{ Задача | {{ Задача | ||
|definition = | |definition = | ||
− | Пусть задана последовательность неотрицательных чисел <tex dpi = "130">a_1, a_2, \ | + | Пусть задана последовательность неотрицательных чисел <tex dpi = "130">a_1, a_2, \ldots, a_n \ (a_i \geqslant 0)</tex>. Требуется расставить знаки <tex dpi = "120">+, \times</tex> и скобки в последовательности, чтобы значение полученного арифметического выражения было максимальным. |
}} | }} | ||
== Решение == | == Решение == | ||
− | Данная задача решается с использованием [[Динамическое_программирование | принципа оптимальности на подотрезках]]. Введём матрицу <tex>d</tex> размером <tex>n \times n</tex>, где <tex>d[i][j]</tex> будет равен максимальному значению, достигаемому на подотрезке <tex dpi = "130">a_i, a_{i+1}, \ | + | Данная задача решается с использованием [[Динамическое_программирование | принципа оптимальности на подотрезках]]. Введём матрицу <tex>d</tex> размером <tex>n \times n</tex>, где <tex>d[i][j]</tex> будет равен максимальному значению, достигаемому на подотрезке <tex dpi = "130">a_i, a_{i+1}, \ldots, a_j</tex>. |
Получаем следующие соотношения: <br /> | Получаем следующие соотношения: <br /> | ||
* <tex>d[i][i] = a_i </tex><br /> | * <tex>d[i][i] = a_i </tex><br /> | ||
− | * <tex>d[i][j] = \max\limits_{\mathop{k = i..j-1}}[\max(d[i][k]+d[k+1][j], d[i][k] \ | + | * <tex>d[i][j] = \max\limits_{\mathop{k = i..j-1}}[\max(d[i][k]+d[k+1][j], d[i][k] \cdot d[k+1][j])] \ (i < j)</tex> <br /> |
Вычислим элементы таблицы <tex>d</tex>, тогда ответом на задачу будет значение <tex>d[1][n]</tex>. | Вычислим элементы таблицы <tex>d</tex>, тогда ответом на задачу будет значение <tex>d[1][n]</tex>. | ||
== Доказательство == | == Доказательство == | ||
Рассмотрим ее, например, в такой формулировке. В арифметическом выражении, операндами которого являются целые числа, а операциями — бинарные арифметические операции <tex>+</tex> и <tex>\times</tex>, расставить скобки так, чтобы результат оказался максимальным. | Рассмотрим ее, например, в такой формулировке. В арифметическом выражении, операндами которого являются целые числа, а операциями — бинарные арифметические операции <tex>+</tex> и <tex>\times</tex>, расставить скобки так, чтобы результат оказался максимальным. | ||
− | Подзадачами, через которые мы будем выражать оптимальное решение, будут задачи об оптимальной расстановке скобок в произведениях наших операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м. Запоминать результат оптимального решения соответствующей подзадачи мы будем в элементе <tex>d[i, j]</tex> матрицы, размером <tex>n\times n</tex>, диагональные элементы которой <tex>(d[i, i])</tex> равны операндам, а для <tex>i > j</tex> все элементы равны <tex>0</tex>. Пусть мы хотим подсчитать значение арифметического выражения для операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м для <tex>i < j</tex>. Если мы предположим, что последней будет выполняться арифметическая операция, расположенная после операнда с номером <tex>k (k < j)</tex> и эта операция — сложение, то результат подсчета будет равен сумме элементов <tex>d[i, k]</tex> и <tex>d[k+1, j]</tex> нашей матрицы. С умножением ситуация несколько более сложная. Так как операндами могут быть и отрицательные числа, то для нахождения максимума из произведений нужно знать не только максимальные, но и минимальные значения для арифметических операций над операндами с <tex>i</tex>-го по <tex>k</tex>-й и с <tex>k+1</tex>-го по <tex>j</tex>-й соответственно. Значит для любой последовательности операндов нужно хранить значение как максимально, так и минимально возможного результата выполнения арифметических операций над ними. Однако, дополнительная матрица для хранения минимальных значений не нужна. Так как для <tex>i > j</tex> матрица <tex>d</tex> не заполнена, мы можем отвести эти элементы для хранения минимумов, только запоминать результат решения этой подзадачи для операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м, мы будем в элементе <tex>d[j, i]</tex>. Тогда в общем случае, если после k-го операнда стоит операция умножения, то максимальный результат будет равен <tex>max(d[i][j], max(d[i][k] + d[k+1][j], d[i][k] \ | + | Подзадачами, через которые мы будем выражать оптимальное решение, будут задачи об оптимальной расстановке скобок в произведениях наших операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м. Запоминать результат оптимального решения соответствующей подзадачи мы будем в элементе <tex>d[i, j]</tex> матрицы, размером <tex>n\times n</tex>, диагональные элементы которой <tex>(d[i, i])</tex> равны операндам, а для <tex>i > j</tex> все элементы равны <tex>0</tex>. Пусть мы хотим подсчитать значение арифметического выражения для операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м для <tex>i < j</tex>. Если мы предположим, что последней будет выполняться арифметическая операция, расположенная после операнда с номером <tex>k (k < j)</tex> и эта операция — сложение, то результат подсчета будет равен сумме элементов <tex>d[i, k]</tex> и <tex>d[k+1, j]</tex> нашей матрицы. С умножением ситуация несколько более сложная. Так как операндами могут быть и отрицательные числа, то для нахождения максимума из произведений нужно знать не только максимальные, но и минимальные значения для арифметических операций над операндами с <tex>i</tex>-го по <tex>k</tex>-й и с <tex>k+1</tex>-го по <tex>j</tex>-й соответственно. Значит для любой последовательности операндов нужно хранить значение как максимально, так и минимально возможного результата выполнения арифметических операций над ними. Однако, дополнительная матрица для хранения минимальных значений не нужна. Так как для <tex>i > j</tex> матрица <tex>d</tex> не заполнена, мы можем отвести эти элементы для хранения минимумов, только запоминать результат решения этой подзадачи для операндов, начиная с <tex>i</tex>-го и заканчивая <tex>j</tex>-м, мы будем в элементе <tex>d[j, i]</tex>. Тогда в общем случае, если после k-го операнда стоит операция умножения, то максимальный результат будет равен <tex>max(d[i][j], max(d[i][k] + d[k+1][j], d[i][k] \cdot d[k+1][j]))</tex>; |
== Реализация == | == Реализация == | ||
− | <font color = "green">// | + | <font color = "green">// a - заданная последовательность из n элементов</font> |
− | '''for''' i = 1 '''to''' n: | + | '''int maxValueOfExpression'''(a, n): |
− | + | '''for''' i = 1 '''to''' n: | |
+ | d[i][i] = a[i] | ||
− | + | '''for''' i = n - 1 '''downto''' 1: | |
− | + | '''for''' j = i + 1 '''to''' n: | |
− | + | '''for''' k = i '''to''' j - 1: | |
− | + | d[i][j] = max(d[i][j], max(d[i][k] + d[k + 1][j], d[i][k] * d[k + 1][j])) | |
− | + | '''return''' d[1][n] | |
− | |||
== Пример == | == Пример == | ||
Строка 64: | Строка 64: | ||
|} | |} | ||
− | == | + | == Восстановление ответа == |
− | * | + | С помощью описанного выше алгоритма можно восстановить оптимальное арифметическое выражение. Для этого нужно в таблице <tex>d</tex> хранить каким способом был получен оптимальный ответ. То есть будем хранить <tex>index</tex> {{---}} границу раздела на 2 подотрезка <tex>(i \ldots index</tex> и <tex>index + 1 \ldots j)</tex> и <tex>operation</tex> {{---}} операцию совершенную над двумя отрезками (<tex>+, \times</tex>). В итоге для каждого отрезка мы будем знать какой знак и куда поставить. Для простоты будем заключать каждый полученный подотрезок в скобки. Таким образом мы получим выражение, в котором полностью расставлены скобки. |
− | + | ||
− | + | Для вышеприведенного примера таблица будет выглядеть так: | |
+ | |||
+ | :{| border = 1; class="wikitable" | ||
+ | |-align = "center" | ||
+ | | <tex>index</tex> | ||
+ | | <tex>j = 1</tex> | ||
+ | | <tex>j = 2</tex> | ||
+ | | <tex>j = 3</tex> | ||
+ | | <tex>j = 4</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 1</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>1</tex> | ||
+ | | <tex>1</tex> | ||
+ | | <tex>2</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 2</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>2</tex> | ||
+ | | <tex>3</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 3</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>3</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 4</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | | <tex>-</tex> | ||
+ | |} | ||
+ | |||
+ | :{| border = 1; class="wikitable" | ||
+ | |-align = "center" | ||
+ | | <tex>operation</tex> | ||
+ | | <tex>j = 1</tex> | ||
+ | | <tex>j = 2</tex> | ||
+ | | <tex>j = 3</tex> | ||
+ | | <tex>j = 4</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 1</tex> | ||
+ | | | ||
+ | | <tex>+</tex> | ||
+ | | <tex>\times</tex> | ||
+ | | <tex>\times</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 2</tex> | ||
+ | | | ||
+ | | | ||
+ | | <tex>+</tex> | ||
+ | | <tex>\times</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 3</tex> | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | <tex>+</tex> | ||
+ | |-align = "center" | ||
+ | | <tex>i = 4</tex> | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | | | ||
+ | |} | ||
+ | |||
+ | После того, как посчитана динамика, можно рекурсивно восстановить ответ: | ||
+ | *Если длина текущего отрезка равна <tex> 1 </tex>, выходим из рекурсии | ||
+ | *Оборачиваем текущий отрезок <tex> l \ldots r </tex> в скобки | ||
+ | *Ставим после элемента последовательности на позиции <tex> d[l][r].index </tex> знак <tex> d[l][r].operation </tex> | ||
+ | *Рекурсивно запускаем от отрезков <tex> l \ldots d[l][r].index </tex> и <tex> d[l][r].index + 1 \ldots r </tex> | ||
+ | |||
+ | Таким образом ответ <tex> ((2+1)\times(1+2))</tex> | ||
+ | |||
+ | == Решение задачи без возможности использования скобок == | ||
+ | При условии задачи, в котором запрещено использовать скобки, вышеописанный алгоритм будет работать некорректно. Дело в том, что если на отрезке <tex>k\ldots j</tex> была использована операция сложения, то мы не можем перемножить результаты на отрезке <tex>i\ldots k - 1</tex> и отрезке <tex>k\ldots j</tex>. Для решения этой проблемы будем использовать две матрицы <tex>d</tex> {{---}} такую же как и раньше, а также <tex>p</tex> <tex>(p[i][j]</tex> {{---}} произведение чисел <tex dpi = "130">a_i, a_{i+1}, \ldots, a_j)</tex>. Тогда получим новую формулу пересчета динамики: | ||
+ | <br /> | ||
+ | * <tex>d[i][j] = \max\limits_{\mathop{k = i..j-1}}[\max(d[i][k] + d[k + 1][j], p[i][k] \cdot p[k + 1][j])] \ (i < j)</tex> <br /> | ||
+ | |||
+ | == См. также == | ||
+ | *[[Задача о порядке перемножения матриц | Задача о порядке перемножения матриц]] | ||
+ | *[[Задача о наибольшей общей подпоследовательности | Задача о наибольшей общей подпоследовательности]] | ||
+ | |||
+ | == Источники информации== | ||
+ | *[http://fvn2009.narod.ru/Olympiads/Rules_Olympiads/Rules22.htm Олимпиадные задачи и решения {{---}} Классические задачи динамического программирования] | ||
+ | * Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4 | ||
[[Категория:Дискретная математика и алгоритмы]] | [[Категория:Дискретная математика и алгоритмы]] | ||
[[Категория:Динамическое программирование]] | [[Категория:Динамическое программирование]] |
Текущая версия на 19:07, 4 сентября 2022
Задача: |
Пусть задана последовательность неотрицательных чисел | . Требуется расставить знаки и скобки в последовательности, чтобы значение полученного арифметического выражения было максимальным.
Решение
Данная задача решается с использованием принципа оптимальности на подотрезках. Введём матрицу размером , где будет равен максимальному значению, достигаемому на подотрезке .
Получаем следующие соотношения:
Вычислим элементы таблицы
, тогда ответом на задачу будет значение .Доказательство
Рассмотрим ее, например, в такой формулировке. В арифметическом выражении, операндами которого являются целые числа, а операциями — бинарные арифметические операции
и , расставить скобки так, чтобы результат оказался максимальным. Подзадачами, через которые мы будем выражать оптимальное решение, будут задачи об оптимальной расстановке скобок в произведениях наших операндов, начиная с -го и заканчивая -м. Запоминать результат оптимального решения соответствующей подзадачи мы будем в элементе матрицы, размером , диагональные элементы которой равны операндам, а для все элементы равны . Пусть мы хотим подсчитать значение арифметического выражения для операндов, начиная с -го и заканчивая -м для . Если мы предположим, что последней будет выполняться арифметическая операция, расположенная после операнда с номером и эта операция — сложение, то результат подсчета будет равен сумме элементов и нашей матрицы. С умножением ситуация несколько более сложная. Так как операндами могут быть и отрицательные числа, то для нахождения максимума из произведений нужно знать не только максимальные, но и минимальные значения для арифметических операций над операндами с -го по -й и с -го по -й соответственно. Значит для любой последовательности операндов нужно хранить значение как максимально, так и минимально возможного результата выполнения арифметических операций над ними. Однако, дополнительная матрица для хранения минимальных значений не нужна. Так как для матрица не заполнена, мы можем отвести эти элементы для хранения минимумов, только запоминать результат решения этой подзадачи для операндов, начиная с -го и заканчивая -м, мы будем в элементе . Тогда в общем случае, если после k-го операнда стоит операция умножения, то максимальный результат будет равен ;Реализация
// a - заданная последовательность из n элементов int maxValueOfExpression(a, n): for i = 1 to n: d[i][i] = a[i] for i = n - 1 downto 1: for j = i + 1 to n: for k = i to j - 1: d[i][j] = max(d[i][j], max(d[i][k] + d[k + 1][j], d[i][k] * d[k + 1][j])) return d[1][n]
Пример
Пусть дана последовательность
. Таблица для неё будет выглядеть так:Восстановление ответа
С помощью описанного выше алгоритма можно восстановить оптимальное арифметическое выражение. Для этого нужно в таблице
хранить каким способом был получен оптимальный ответ. То есть будем хранить — границу раздела на 2 подотрезка и и — операцию совершенную над двумя отрезками ( ). В итоге для каждого отрезка мы будем знать какой знак и куда поставить. Для простоты будем заключать каждый полученный подотрезок в скобки. Таким образом мы получим выражение, в котором полностью расставлены скобки.Для вышеприведенного примера таблица будет выглядеть так:
После того, как посчитана динамика, можно рекурсивно восстановить ответ:
- Если длина текущего отрезка равна , выходим из рекурсии
- Оборачиваем текущий отрезок в скобки
- Ставим после элемента последовательности на позиции знак
- Рекурсивно запускаем от отрезков и
Таким образом ответ
Решение задачи без возможности использования скобок
При условии задачи, в котором запрещено использовать скобки, вышеописанный алгоритм будет работать некорректно. Дело в том, что если на отрезке
См. также
Источники информации
- Олимпиадные задачи и решения — Классические задачи динамического программирования
- Томас Х. Кормен, Чарльз И. Лейзерсон, Рональд Л. Ривест, Клиффорд Штайн Алгоритмы: построение и анализ — 2-е изд. — М.: «Вильямс», 2007. — с. 459. — ISBN 5-8489-0857-4