Добавлен: 03.12.2023
Просмотров: 79
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
Министерство науки и высшего образования Российской ФедерацииФГАОУ ВО «УрФУ имени первого Президента России Б.Н. Ельцина»Оценка работы______________Динамическое программированиеОТЧЕТВид практики: Учебная практикаТип практики: Учебная практика, научно-исследовательская работа (получение первичных навыков научно-исследовательской работы)Руководитель практики от предприятия (организации): Студент:Специальность (направление подготовки): Группа: Екатеринбург 20 ____Теоретическая частьДинамическое программирование – это метод решения оптимизационных задач, основанный на разбиении исходной задачи на более мелкие подзадачи, рекуррентно связанные между собой.Динамическое программирование может быть применено к широкому кругу задач, включая нахождение оптимального маршрута в графе, поиск наибольшей общей подпоследовательности двух строк, нахождение наибольшего подмассива в массиве чисел и многие другие.Существует два основных метода вычислений результата в динамическом программировании.
Для демонстрации применения метода динамического программирования приведу следующую задачу.Исходные данныеВ первой строке через пробел даны два целых числа n и k — количество рядов в салоне самолёта и количество людей, купивших билеты вместе, соответственно (1 ≤ n ≤ 100, 1 ≤ k ≤ 6 · n).Далее следуют n строк, каждая из которых содержит описание очередного ряда — строку длины 9 символов. Символ «.» (код 46) означает, что кресло свободно, «*» (код 42) — что занято. Первые три символа описывают занятость мест A,B,C. Затем следуют ещё три символа «|_|» (коды 124, 95, 124; без кавычек) — описание прохода между креслами. Последние три символа описывают занятость мест D,E,F.Гарантируется, что число k не превосходит количество свободных мест в салоне.РезультатЕсли рассадить всех людей нужным способом невозможно, в единственной строке выведите слово «PORAZHENIE». Иначе в первой строке выведите слово «POBEDA», затем в следующих k строках выведите номера мест, на которые нужно посадить людей, в любом порядке. Номера мест следует выводить в формате, описанном в условии. Если возможных ответов несколько, выведите любой.Неформальное изложение алгоритма1. Для начала считываем данные. Данные о ряде удобно хранить в виде десятичного целого числа s (0 ≤ s ≤ 64) – двоичное представление которого является “маской” ряда (1 – место занято, 0 - свободно). Имеем три массива.
prev[i][mask] = front
private lateinit var dp: Array
fun complete() {
val (rowNumber, peopleNumber) = readLine()!!.split(" ").map { it.toInt() }
seats = IntArray(rowNumber + 2) { 0 }
prev = Array(rowNumber + 2) { IntArray(64) { 0 } }
dp = Array(rowNumber + 2) { IntArray(64) { 0 } }
for (i in 1..rowNumber) {
val line = readLine()!!.split("|_|").joinToString("").reversed()
line.forEachIndexed { index, char ->
val bit = if (char == '*') 1 else 0
seats[i] = seats[i] or (bit shl index)
}
}
for (i in 1..rowNumber + 1) {
for (front in 0..63) {
for (mask in 0..63) {
if (isValid(mask, front, i, seats)
&& dp[i][mask] < dp[i - 1][front] + count(mask)
) {
dp[i][mask] = dp[i - 1][front] + count(mask)
prev[i][mask] = front
}
}
}
}
if (dp[rowNumber + 1][0] < peopleNumber) {
println("PORAZHENIE")
} else {
println("POBEDA")
if (peopleNumber != 0) {
var counter = 0
var m = 0
for (i in rowNumber + 1 downTo 1) {
m = prev[i][m]
for (j in 0..5) {
if (isSet(j, m) && counter < peopleNumber) {
println("${i - 1}${'F' - j}")
counter++
}
}
}
}
}
}
fun isValid(mask: Int, frontMask: Int, row: Int, seats: IntArray): Boolean {
val notOccupied = mask and seats[row] == 0
val hasNeighboursNextTo = hasNeighboursNextTo(mask)
val hasNeighboursFront = hasNeighboursRow(mask, frontMask)
return notOccupied && !hasNeighboursNextTo && !hasNeighboursFront
}
/* Проверяем, сидятли 2 человекав [mask] рядом, несчитаяпрохода. */
fun hasNeighboursNextTo(mask: Int): Boolean {
for (k in 0..4) {
if (k != 2 && (mask and (3 shl k) == (3 shl k)))
return true
}
return false
}
/* Проверяемкаждоепотенциальноеместонаналичиесоседейвсоседнемряду. */
fun hasNeighboursRow(mask: Int, frontMask: Int): Boolean {
for (j in 0..5) {
if (isSet(j, mask) && checkFront(j, frontMask)) return true
}
return false
}
/* Считаетединичкив [mask], т.е. занятыеместа. */
fun count(mask: Int): Int {
var counter = 0
for (i in 0..5) {
val hasBit = mask and (1 shl i) > 0
counter += if (hasBit) 1 else 0
}
return counter
}
/* Возвращает true, еслиестьсоседивпереднемряду. */
fun checkFront(seat: Int, frontMask: Int): Boolean {
return isSet(seat, frontMask)
|| isSet(seat - 1, frontMask) && seat != 3
|| isSet(seat + 1, frontMask) && seat != 2
}
/** Проверяетустановленли i-ыйбитмаски m */
val isSet: (i: Int, m: Int) -> Boolean = { i, m -> m and (1 shl i) != 0 }
}
Используя подход динамического программирования удалось добиться сложности O(n).
-
Рекурсивный - для каждой задачи можно составить рекуррентную формулу, которая будет описывать её решение. -
Последовательный
-
разбить исходную задачу на подзадачи -
определить оптимальную подструктуру -
разработать алгоритм для вычисления результатов.
Для демонстрации применения метода динамического программирования приведу следующую задачу.Исходные данныеВ первой строке через пробел даны два целых числа n и k — количество рядов в салоне самолёта и количество людей, купивших билеты вместе, соответственно (1 ≤ n ≤ 100, 1 ≤ k ≤ 6 · n).Далее следуют n строк, каждая из которых содержит описание очередного ряда — строку длины 9 символов. Символ «.» (код 46) означает, что кресло свободно, «*» (код 42) — что занято. Первые три символа описывают занятость мест A,B,C. Затем следуют ещё три символа «|_|» (коды 124, 95, 124; без кавычек) — описание прохода между креслами. Последние три символа описывают занятость мест D,E,F.Гарантируется, что число k не превосходит количество свободных мест в салоне.РезультатЕсли рассадить всех людей нужным способом невозможно, в единственной строке выведите слово «PORAZHENIE». Иначе в первой строке выведите слово «POBEDA», затем в следующих k строках выведите номера мест, на которые нужно посадить людей, в любом порядке. Номера мест следует выводить в формате, описанном в условии. Если возможных ответов несколько, выведите любой.Неформальное изложение алгоритма1. Для начала считываем данные. Данные о ряде удобно хранить в виде десятичного целого числа s (0 ≤ s ≤ 64) – двоичное представление которого является “маской” ряда (1 – место занято, 0 - свободно). Имеем три массива.
-
seats[i] – схема i-ого ряда -
dp[i][mask] - максимальное число пассажиров, мы можем посадить на первых i рядах, где mask – конкретное расположение пассажиров в ряду i -
prev[i][mask] –оптимальная маска для ряда i-1
-
front – оптимальная маска для переднего (i - 1) ряда -
count(mask) – количество занятых мест новыми пассажирами
prev[i][mask] = front
-
запоминаем оптимальную маску предыдущего ряда для сбора ответа
-
если максимальное число пассажиров, что могут сесть в самолете меньше числа пассажиров k –
-
иначе “POBEDA”, в обратном порядке собираем ответ – маски для ряда i, каждая из которых является индексом к маске (i - 1)
private lateinit var dp: Array
fun complete() {
val (rowNumber, peopleNumber) = readLine()!!.split(" ").map { it.toInt() }
seats = IntArray(rowNumber + 2) { 0 }
prev = Array(rowNumber + 2) { IntArray(64) { 0 } }
dp = Array(rowNumber + 2) { IntArray(64) { 0 } }
for (i in 1..rowNumber) {
val line = readLine()!!.split("|_|").joinToString("").reversed()
line.forEachIndexed { index, char ->
val bit = if (char == '*') 1 else 0
seats[i] = seats[i] or (bit shl index)
}
}
for (i in 1..rowNumber + 1) {
for (front in 0..63) {
for (mask in 0..63) {
if (isValid(mask, front, i, seats)
&& dp[i][mask] < dp[i - 1][front] + count(mask)
) {
dp[i][mask] = dp[i - 1][front] + count(mask)
prev[i][mask] = front
}
}
}
}
if (dp[rowNumber + 1][0] < peopleNumber) {
println("PORAZHENIE")
} else {
println("POBEDA")
if (peopleNumber != 0) {
var counter = 0
var m = 0
for (i in rowNumber + 1 downTo 1) {
m = prev[i][m]
for (j in 0..5) {
if (isSet(j, m) && counter < peopleNumber) {
println("${i - 1}${'F' - j}")
counter++
}
}
}
}
}
}
fun isValid(mask: Int, frontMask: Int, row: Int, seats: IntArray): Boolean {
val notOccupied = mask and seats[row] == 0
val hasNeighboursNextTo = hasNeighboursNextTo(mask)
val hasNeighboursFront = hasNeighboursRow(mask, frontMask)
return notOccupied && !hasNeighboursNextTo && !hasNeighboursFront
}
/* Проверяем, сидятли 2 человекав [mask] рядом, несчитаяпрохода. */
fun hasNeighboursNextTo(mask: Int): Boolean {
for (k in 0..4) {
if (k != 2 && (mask and (3 shl k) == (3 shl k)))
return true
}
return false
}
/* Проверяемкаждоепотенциальноеместонаналичиесоседейвсоседнемряду. */
fun hasNeighboursRow(mask: Int, frontMask: Int): Boolean {
for (j in 0..5) {
if (isSet(j, mask) && checkFront(j, frontMask)) return true
}
return false
}
/* Считаетединичкив [mask], т.е. занятыеместа. */
fun count(mask: Int): Int {
var counter = 0
for (i in 0..5) {
val hasBit = mask and (1 shl i) > 0
counter += if (hasBit) 1 else 0
}
return counter
}
/* Возвращает true, еслиестьсоседивпереднемряду. */
fun checkFront(seat: Int, frontMask: Int): Boolean {
return isSet(seat, frontMask)
|| isSet(seat - 1, frontMask) && seat != 3
|| isSet(seat + 1, frontMask) && seat != 2
}
/** Проверяетустановленли i-ыйбитмаски m */
val isSet: (i: Int, m: Int) -> Boolean = { i, m -> m and (1 shl i) != 0 }
}
Используя подход динамического программирования удалось добиться сложности O(n).