Алгоритм отбрасывания бомб

У меня есть матрица n x m, состоящая из неотрицательных целых чисел. Например:

2 3 4 7 1
1 5 2 6 2
4 3 4 2 1
2 1 2 4 1
3 1 3 4 1
2 1 4 3 2
6 9 1 6 4

"Бросание бомбы" уменьшается на один номер ячейки-мишени и все восемь ее соседей, до минимума.

x x x 
x X x
x x x

Что такое алгоритм, который определял бы минимальное количество бомб, необходимых для сокращения всех ячеек до нуля?

Вариант B (из-за того, что я не являюсь внимательным читателем)

На самом деле первая версия проблемы не та, на которую я ищу ответ. Я не тщательно читал целую задачу, есть дополнительные ограничения, скажем:

Как насчет простой проблемы, когда последовательность в строке должна быть не увеличивающейся:

8 7 6 6 5 Возможная последовательность ввода

7 8 5 5 2 невозможно, так как 7 → 8 растет в последовательности.

Возможно, поиск ответа для "более простого" случая поможет найти решение для более сложного.

PS: Я считаю, что, когда у нас есть несколько ситуаций, требуется минимальная бомба для очистки верхней линии, мы выбираем ту, которая использует большинство бомб на "левой стороне" строки. Какое-либо доказательство, которое может быть правильным?

211
задан abc 08 марта '13 в 20:47
источник поделиться

32 ответов

  • 1
  • 2

Существует способ уменьшить это до простой суб-проблемы.

Есть две части объяснения, алгоритм и причина, по которой алгоритм обеспечивает оптимальное решение. Первый не будет иметь смысла без второго, так что я начните с того, почему.

Если вы думаете о бомбардировке прямоугольника (предположим, что большой прямоугольник - никаких случаев кромки) вы можете видеть, что единственный способ уменьшить полый прямоугольник квадратов на периметр до 0 - бомбить по периметру или бомбить полый прямоугольник квадраты только внутри периметра. Я назову слой периметра 1, а прямоугольник внутри него - слой 2.

Важное понимание заключается в том, что нет слоя бомбардировки точки 1, поскольку "радиус взрыва", который вы получаете от этого, всегда содержится в радиусе взрыва другой квадрат из слоя 2. Вы должны быть в состоянии легко убедить себя в этом.

Итак, мы можем свести проблему к поиску оптимального способа бомбардировки периметра, тогда мы можем повторить это, пока все квадраты не равны 0.

Но, конечно, это не всегда найдет оптимальное решение, если возможно бомбить прочь по периметру менее оптимальным образом, но, используя X дополнительных бомб, сделайте проблема упрощения внутреннего слоя нa > X-бомбах. Итак, если мы позвоним один слой разрешителя, если мы разместим дополнительные Х-бомбы где-нибудь в слое 2 (просто внутри слоя 1), мы можем уменьшить усилия более позднего взрыва слоя 2 более чем ИКС? Другими словами, мы должны доказать, что мы можем быть жадными в уменьшении внешнего Периметр.

Но мы знаем, что можем быть жадными. Потому что никакая бомба в слое 2 никогда не может быть больше эффективно снижая уровень 2 до 0, чем стратегически размещенная бомба в слое 3. И по той же причине, что и раньше, - всегда есть бомба, которую мы можем разместить в слое 3, будет влиять на каждый квадрат слоя 2, который может помещать бомба, размещенная в слое 2. Таким образом, он может никогда не навредить нам быть жадным (в этом смысле жадным).

Итак, все, что нам нужно сделать, это найти оптимальный способ уменьшить разрешение до 0 путем бомбардировки следующий внутренний слой.

Нам никогда не причиняли вреда, сначала бомбив угол до 0, потому что только угол внутреннего слоя может достичь этого, поэтому у нас действительно нет выбора (и любая бомба по периметру, которая может достигать угла, имеет радиус взрыва содержащихся в радиусе взрыва от угла внутреннего слоя).

Как только мы это сделаем, квадраты по периметру, расположенные рядом с углом 0, могут быть достигнуты только на 2 квадрата от внутреннего слоя:

0       A       B

C       X       Y

D       Z

В этот момент периметр является фактически замкнутой одномерной петлей, потому что любая бомба уменьшит 3 смежных квадрата. За исключением некоторой странности вблизи углов - X может "ударить" A, B, C и D.

Теперь мы не можем использовать трюки радиуса взрыва - ситуация каждого квадрата симметрична, за исключением странных углов, и даже там радиус взрыва не является подмножеством другого. Заметим, что если бы это была линия (как обсуждает полковник Паника) вместо замкнутой петли, решение тривиально. Конечные точки должны быть уменьшены до 0, и вам никогда не нападет бомбить точки, прилегающие к конечным точкам, опять же, потому что радиус взрыва является надмножеством. Как только вы сделали свою конечную точку 0, у вас все еще есть новая конечная точка, поэтому повторите (пока линия не будет равна 0).

Итак, если мы можем оптимально уменьшить один квадрат в слое до 0, у нас есть алгоритм (потому что мы отрезали цикл и теперь имеем прямую линию с конечными точками). Я считаю, что бомбардировка рядом с квадратом с наименьшим значением (дает вам 2 варианта), так что наивысшее значение в пределах 2 квадратов самого низкого значения - это минимально возможное (возможно, вам придется разбить вашу бомбардировку, чтобы справиться с этим) будет оптимальным, но я нет (еще?) есть доказательство.

38
ответ дан psr 09 марта '13 в 3:31
источник поделиться

Поля говорит: "Если вы не можете решить проблему, тогда есть более легкая проблема, которую вы можете решить: найдите ее".

Очевидной более простой проблемой является одномерная задача (когда сетка представляет собой одну строку). Начнем с простейшего алгоритма - жадно бомбить самую большую цель. Когда это не так?

Учитывая 1 1 1, жадный алгоритм безразличен, к какой ячейке он бомбит первым. Конечно, центральная ячейка лучше - она ​​нулирует все три ячейки сразу. Это предполагает новый алгоритм A, "бомба, чтобы минимизировать оставшуюся сумму". Когда этот алгоритм идет не так?

Учитывая 1 1 2 1 1, алгоритм A безразличен между бомбардировкой 2-й, 3-й или 4-й ячеек. Но бомбить вторую ячейку, чтобы оставить 0 0 1 1 1, лучше, чем бомбить 3-ю ячейку, чтобы оставить 1 0 1 0 1. Как это исправить? Проблема с бомбардировкой 3-й ячейки заключается в том, что она оставляет работу слева и работает вправо, что нужно делать отдельно.

Как насчет "бомбы, чтобы свести к минимуму оставшуюся сумму, но максимизируйте минимум слева (где мы бомбили) плюс минимум вправо". Вызовите этот алгоритм B. Когда этот алгоритм идет не так?


Изменить: прочитав комментарии, я согласен, что гораздо более интересная проблема будет заключаться в изменении одномерной проблемы, так что концы объединяются. Хотелось бы увидеть прогресс на этом.

25
ответ дан Colonel Panic 08 марта '13 в 22:53
источник поделиться

Мне пришлось остановиться только на частичном решении, так как я был вне времени, но, надеюсь, даже это частичное решение дает некоторое представление об одном потенциальном подходе к решению этой проблемы.

Когда вы сталкиваетесь с серьезной проблемой, мне нравится придумывать более простые проблемы, чтобы развить интуицию о проблемном пространстве. Здесь первый шаг, который я предпринял, заключался в том, чтобы уменьшить эту двумерную проблему до одномерной проблемы. Рассмотрим строку:

0 4 2 1 3 0 1

Так или иначе, вы знаете, что вам нужно будет бомбить на месте или вокруг места 4 4 раза, чтобы получить его до 0. Поскольку слева от пятна меньше, нет никакой пользы для бомбардировки 0 или 4 над бомбардировкой 2. На самом деле я считаю (но не имею строгого доказательства), что бомбардировка 2 до тех пор, пока пятно 4 не снизится до 0, по крайней мере так же хорошо, как любая другая стратегия, чтобы получить 4 до 0. Можно продолжить вниз по линии слева направо в стратегии, подобной этой:

index = 1
while index < line_length
  while number_at_index(index - 1) > 0
    bomb(index)
  end
  index++
end
# take care of the end of the line
while number_at_index(index - 1) > 0
  bomb(index - 1)
end

Несколько примеров бомбардировок:

0 4[2]1 3 0 1
0 3[1]0 3 0 1
0 2[0]0 3 0 1
0 1[0]0 3 0 1
0 0 0 0 3[0]1
0 0 0 0 2[0]0
0 0 0 0 1[0]0
0 0 0 0 0 0 0

4[2]1 3 2 1 5
3[1]0 3 2 1 5
2[0]0 3 2 1 5
1[0]0 3 2 1 5
0 0 0 3[2]1 5
0 0 0 2[1]0 5
0 0 0 1[0]0 5
0 0 0 0 0 0[5]
0 0 0 0 0 0[4]
0 0 0 0 0 0[3]
0 0 0 0 0 0[2]
0 0 0 0 0 0[1]
0 0 0 0 0 0 0

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

Следующий шаг по сложности, когда этот поиск, по крайней мере, как хороший, по-прежнему возможен, находится на краю доски. Для меня ясно, что никогда не существует какой-либо строгости для бомбардировки внешнего края; вам лучше бомбить место в одном и получить три других места бесплатно. Учитывая это, мы можем сказать, что бомбардировка кольца внутри краю не хуже, чем бомбардировка края. Более того, мы можем объединить это с интуицией, что бомбардировка правой части внутри края фактически является единственным способом получить краевые пространства до нуля. Более того, тривиально просто определить оптимальную стратегию (поскольку она находится в по крайней мере, так же хорошо, как и любая другая стратегия), чтобы получить угловые числа до 0. Мы ставим все это вместе и можем приблизиться к решению в двумерном пространстве.

Учитывая наблюдение за углами, мы можем с уверенностью сказать, что мы знаем оптимальную стратегию перехода от любой начальной доски к доске с нулями по всем углам. Это пример такой платы (я взял номера из двух линейных плат выше). Я поместил некоторые пробелы по-разному, и я объясню, почему.

0 4 2 1 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

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

Мы могли бы применить линейную стратегию сверху (бомбардировка соответствующих пространств в строке x), касающуюся нас только с верхней строкой и ничем иным. Это будет выглядеть примерно так:

0 4 2 1 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 3 1 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 2 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 1 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 0 0 0 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

Недостаток этого подхода становится очень очевидным в последних двух бомбардировках. Ясно, что, учитывая, что единственными местами бомб, которые уменьшают цифру 4 в первом столбце во второй строке, являются первые x и y. Последние два бомбардировки явно уступают только бомбардировке первого x, который сделал бы то же самое (относительно первого пятна в верхнем ряду, которого у нас нет другого способа очистки). Поскольку мы продемонстрировали, что наша текущая стратегия субоптимальна, очевидно, необходима модификация в стратегии.

В этот момент я могу сделать шаг назад по сложности и сосредоточиться только на одном углу. Рассмотрим это:

0 4 2 1
4 x y a
2 z . .
1 b . .

Ясно, что единственный способ получить пробелы с 4 до нуля - это бомбить некоторую комбинацию x, y и z. С некоторой акробатикой, на мой взгляд, я уверен, что оптимальным решением является бомба x три раза, а затем a, затем b. Теперь нужно выяснить, как я достиг этого решения, и если он раскрывает любую интуицию, которую мы можем использовать, чтобы решить эту локальную проблему. Я замечаю, что нет бомбежек пробелов y и z. Попытка найти угол, где бомбардировка этих пространств имеет смысл, дает угол, который выглядит следующим образом:

0 4 2 5 0
4 x y a .
2 z . . .
5 b . . .
0 . . . .

Для этого мне ясно, что оптимальным решением является бомба y 5 раз и z 5 раз. Отпустите еще один шаг.

0 4 2 5 6 0 0
4 x y a . . .
2 z . . . . .
5 b . . . . .
6 . . . . . .
0 . . . . . .
0 . . . . . .

Здесь, похоже, интуитивно понятно, что оптимальным решением является бомба a и b 6 раз, а затем x 4 раза.

Теперь это станет игрой о том, как превратить эти интуиции в принципы, на которых мы можем опираться.

Надеемся, что мы продолжим!

12
ответ дан Steven Xu 09 марта '13 в 0:30
источник поделиться

Для обновленного вопроса простой жадный алгоритм дает оптимальный результат.

Бросьте A [0,0] бомбы в ячейку A [1,1], затем опустите A [1,0] бомбы в ячейку A [2,1] и продолжите этот процесс вниз. Чтобы очистить нижний левый угол, сбросьте максимальную (A [N-1,0], A [N-2,0], A [N-3,0]) бомбы в ячейку A [N-2,1]. Это полностью очистит первые 3 столбца.

При таком же подходе чистые столбцы 3,4,5, затем столбцы 6,7,8 и т.д.

К сожалению, это не помогает найти решение исходной проблемы.


"Более крупная" проблема (без ограничения "не увеличивающей" ) может оказаться доказанной NP-жесткой. Вот эскиз доказательства.

Предположим, что у нас есть плоский граф степени до 3. Пусть найдется минимум вершинная обложка для этого графика. Согласно статье в Википедии, эта проблема NP-hard для планарных графов степени до 3. Это может быть доказано путем сокращения от Planar 3SAT. И твердость Planar 3SAT - сокращением от 3SAT. Оба эти доказательства представлены в последних лекциях в "Алгоритмические нижние границы" проф. Эрик Демейн (лекции 7 и 9).

Если мы разделим некоторые ребра исходного графа (левый график на диаграмме), каждый из которых имеет четное число дополнительных узлов, то полученный граф (правый граф на диаграмме) должен иметь точно такое же минимальное покрытие вершин для исходных вершин, Такое преобразование позволяет выровнять вершины графа с произвольными позициями на сетке.

enter image description here

Если мы поместим вершины графа только четным строкам и столбцам (таким образом, чтобы ни один из двух ребер, инцидентных одной вершине, не образовал острый угол), вставьте "единицы", где есть ребро, и вставьте "нули" в другие сетки, мы могли бы использовать любое решение для исходной задачи, чтобы найти минимальное покрытие вершин.

10
ответ дан Evgeny Kluev 10 марта '13 в 18:29
источник поделиться

Это был бы жадный подход:

  • Вычислить матрицу "оценки" порядка n X m, где оценка [i] [j] - это суммарный вывод точек в матрице, если бомбардировать положение (i, j). (Максимальный балл точки равен 9, а мин - 0)

  • Перемещение по строке, найти и выбрать первую позицию с наивысшим счетом (скажем (i, j)).

  • Бомба (i, j). Увеличьте количество бомб.

  • Если все элементы исходной матрицы не равны нулю, то переходим к 1.

У меня есть сомнения, что это оптимальное решение.

Edit:

Жадный подход, который я опубликовал выше, хотя он работает, скорее всего, не дает нам оптимального решения. Поэтому я решил добавить некоторые элементы DP к нему.

Я думаю, мы можем согласиться, что в любой момент времени одна из позиций с наивысшим "счетом" (оценка [i] [j] = общий вывод точек, если бомбардировка (i, j)), Исходя из этого предположения, здесь новый подход:

NumOfBombs (M): (возвращает минимальное количество взрывов)

  • Учитывая матрицу M порядка n X m. Если все элементы из M равны нулю, тогда возвращаем 0.

  • Вычислить матрицу "оценка" M.

    Пусть k различных позиций P1, P2,... Pk (1 <= k <= n * m), являются позициями в M с наивысшими баллами.

  • return (1 + min (NumOfBombs (M1), NumOfBombs (M2),..., NumOfBombs (Mk)))

    где M1, M2,..., Mk - результирующие матрицы, если мы бомбили позиции P1, P2,..., Pk соответственно.

Кроме того, если мы хотим, чтобы порядок позиций был в nuke в дополнение к этому, нам нужно было бы отслеживать результаты "min".

9
ответ дан SidR 08 марта '13 в 21:39
источник поделиться

Вы можете представить эту проблему как проблему целочисленное программирование. (это всего лишь одно из возможных решений для решения этой проблемы)

Наличие точек:

a b c d
e f g h
i j k l
m n o p

можно записать 16 уравнений, где, например, для точки f

f <= ai + bi + ci + ei + fi + gi + ii + ji + ki   

минимизировано по сумме всех индексов и целочисленного решения.

Решение, конечно, является суммой этих индексов.

Это можно еще упростить, установив все xi на границах 0, поэтому в этом примере вы получите уравнение 4 + 1.

Проблема в том, что для решения таких задач нет тривиального алгоритма. Я не эксперт в этом, но решая эту проблему, поскольку линейное программирование NP трудно.

9
ответ дан Luka Rahne 08 марта '13 в 22:03
источник поделиться

Это частичный ответ, я пытаюсь найти нижнюю границу и верхнюю границу, которая может быть возможным количеством бомб.

В 3x3 и меньшей доске решение тривиально всегда является самой большой пронумерованной ячейкой.

В платах размером больше 4x4 первая очевидная нижняя граница - это сумма углов:

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*

однако вы устраиваете бомбу, невозможно очистить эту плату 4x4 с менее чем 2 + 1 + 6 + 4 = 13 бомбами.

В других ответах упоминалось, что размещение бомбы на втором в угол, чтобы устранить угол, никогда не бывает хуже, чем размещение бомбы на самом углу, поэтому дается доска:

*2* 3  4  7 *1*
 1  5  2  6  2
 4  3  4  2  1
 2  1  2  4  1
 3  1  3  4  1
 2  1  4  3  2
*6* 9  1  6 *4*

Мы можем обнулить углы, поставив бомбы на второй угол, чтобы дать новую доску:

 0  1  1  6  0
 0  3  0  5  1
 2  1  1  1  0
 2  1  2  4  1
 0  0  0  0  0
 0  0  0  0  0
 0  3  0  2  0

Пока все хорошо. Нам нужно 13 бомб, чтобы очистить углы.

Теперь обратите внимание на цифры 6, 4, 3 и 2, отмеченные ниже:

 0  1  1 *6* 0
 0  3  0  5  1
 2  1  1  1  0
*2* 1  2 *4* 1
 0  0  0  0  0
 0  0  0  0  0
 0 *3* 0  2  0

Невозможно бомбить любые две из этих ячеек с использованием одной бомбы, поэтому минимальная бомба увеличилась на 6 + 4 + 3 + 2, поэтому, добавив к числу бомб, которые мы использовали для очистки углов, мы получаем что минимальное количество бомб, необходимых для этой карты, стало 28 бомб. Невозможно очистить эту карту с менее чем 28 бомбами, это нижняя граница для этой карты.

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

Когда жадные и метод поиска минимальной границы, о которой я говорил выше, не сходятся, я думаю, вам нужно вернуться к проверке всех комбинаций.

Алгоритм поиска нижней границы следующий:

  • Выберите элемент с наивысшим номером, назовите его P.
  • Отметьте все ячейки в двух шагах от P и P как неприступные.
  • Добавить P в список minimums.
  • Повторяйте до шага 1, пока все ячейки не будут недоступны.
  • Составьте список minimums, чтобы получить нижнюю границу.
9
ответ дан Lie Ryan 09 марта '13 в 1:56
источник поделиться

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

Обратите внимание, что в левом столбце указаны самые высокие числа. Поэтому любое оптимальное решение должно сначала свести этот столбец к нулю. Таким образом, мы можем выполнить над этим столбцом прорыв 1-D, уменьшая каждый элемент в нем до нуля. Мы позволяем бомбам падать на второй столбец, чтобы они наносили максимальный урон. По-моему, есть много сообщений, касающихся дела 1D, поэтому я чувствую себя в безопасности, пропуская этот случай. (Если вы хотите, чтобы я описал это, я могу.). Из-за уменьшения свойства три левых столбца будут сведены к нулю. Но мы предположительно будем использовать минимальное количество бомб, потому что левый столбец должен быть обнулен.

Теперь, когда левый столбец обнуляется, мы просто обрезаем три левых столбца, которые теперь обнуляются и повторяются с уменьшенной теперь матрицей. Это должно дать нам оптимальное решение, поскольку на каждом этапе мы используем предположительно минимальное количество бомб.

8
ответ дан nneonneo 10 марта '13 в 23:17
источник поделиться

Нет необходимости преобразовывать проблему в линейные подзадачи.

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

В данном примере есть четыре угла, {2, 1, 6, 4}. Для каждого угла нет лучшего движения, чем бомбить диагональ ячейки до угла, поэтому мы знаем, что наши первые 2 + 1 + 6 + 4 = 13 бомбардировки должны быть в этих диагональных ячейках. После бомбардировки мы остаемся с новой матрицей:

2 3 4 7 1      0 1 1 6 0      0 1 1 6 0     1 1 6 0     0 0 5     0 0 0 
1 5 2 6 2      0 3 0 5 1      0 3 0 5 1  => 1 0 4 0  => 0 0 3  => 0 0 0  
4 3 4 2 1      2 1 1 1 0      2 1 1 1 0     0 0 0 0     0 0 0     0 0 3  
2 1 2 4 1  =>  2 1 2 4 1  =>  2 1 2 4 1     0 0 3 0     0 0 3      
3 1 3 4 1      0 0 0 0 0      0 0 0 0 0 
2 1 4 3 2      0 0 0 0 0      0 0 0 0 0 
6 9 1 6 4      0 3 0 2 0      0 0 0 0 0 

После первых 13 взрывов мы используем эвристику для устранения 3 0 2 с помощью трех взрывов. Теперь у нас есть 2 новых угла, {2, 1} в 4-й строке. Мы бомбили эти, еще 3 взрыва. Теперь мы уменьшили матрицу до 4 х 4. Есть один угол, верхний левый. Мы бомбили это. Теперь у нас осталось 2 угла, {5, 3}. Поскольку 5 - самый большой угол, мы сначала бомбили, 5 бомбардировок, а затем, наконец, бомбили 3 в другом углу. Общее число составляет 13 + 3 + 3 + 1 + 5 + 3 = 28.

3
ответ дан Tyler Durden 09 марта '13 в 1:30
источник поделиться

Это жадное решение кажется правильным:

Как указано в комментариях, в 2D это не удастся. Но, возможно, вы можете улучшить его.

Для 1D:
 Если есть хотя бы 2 номера, вам не нужно стрелять в самый левый, потому что стрельба на второй не хуже. Поэтому стреляйте во второй, а сначала не 0, потому что вам нужно это делать. Переместитесь в следующую ячейку. Не забывайте о последней ячейке.

Код С++:

void bombs(vector<int>& v, int i, int n){
    ans += n;
    v[i] -= n;
    if(i > 0)
        v[i - 1] -= n;
    if(i + 1< v.size())
        v[i + 1] -= n;
}

void solve(vector<int> v){
    int n = v.size();
    for(int i = 0; i < n;++i){
        if(i != n - 1){
            bombs(v, i + 1, v[i]);
        }
        else
            bombs(v, i, v[i])
    }
}

Итак, для 2D:
Опять же: вам не нужно снимать в первом ряду (если есть второй). Так стреляй во второй. Решите задачу 1D для первой строки. (потому что вам нужно сделать его нулевым). Опускаться. Не забывайте последнюю строку.

3
ответ дан RiaD 09 марта '13 в 0:26
источник поделиться

Кажется, что подход с линейным программированием может быть очень полезен здесь.

Пусть P m x n - матрица со значениями позиций:

Matrix of positions

Теперь давайте определим матрицу бомб B (x, y) m x n, с 1 ≤ x ≤ m, 1 ≤ y ≤ n, как показано ниже

Bomb matrix

таким образом, что

Values of positions in bomb matrix

Например:

B(3, 3)

Итак, мы смотрим на матрицу B m x n= [b ij], что

  • Может быть определена как сумма матриц бомб:

    B as a sum of bomb matrices

    (q ij было бы тогда количеством бомб, которые мы упали бы в позиции p ij)

  • p ij - b ij ≤ 0 (чтобы быть более succint, скажем, P-B ≤ 0)

Кроме того, B должен минимизировать сумму sum of quantities of bombs.

Мы также можем записать B как уродливую матрицу вперед:

B as a matrix of sum of quantities

и поскольку P - B ≤ 0 (что означает P ≤ B), мы имеем следующую систему линейных неравенств ниже:

Relationship between number of bombs dropped and values in positions

Будучи q mn x 1, определяемым как

Vector of quantities

p mn x 1, определяемый как

Values of P distributed as a vector

Можно сказать, что у нас есть система Система, представленная как произведение матриц http://latex.codecogs.com/gif.download?S% 5Cmathbf% 7Bq% 7D & space;% 5Cge & space;% 5Cmathbf% 7Bp% 7D является S mn x mn матрица, подлежащая обратному решению для решения системы. Я не расширил его сам, но я считаю, что это должно быть легко сделать в коде.

Теперь у нас есть минимальная проблема, которую можно сформулировать как

The system we have to solve

Я считаю, что это что-то простое, почти тривиальное решение с помощью чего-то вроде симплекс-алгоритм (есть этот довольно классный документ об этом). Тем не менее, я почти не знаю линейного программирования (я расскажу об этом на Курсере, но это будет только в будущем...), у меня были некоторые головные боли, пытающиеся понять это, и у меня есть огромная внештатная работа, чтобы закончить, поэтому я просто сдавайся здесь. Может быть, я сделал что-то неправильно в какой-то момент или что он не может идти дальше, но я считаю, что этот путь может в конечном итоге привести к решению. Во всяком случае, я беспокоюсь за ваши отзывы.

(Особая благодарность этот удивительный сайт для создания изображений из выражений LaTeX)

3
ответ дан brandizzi 09 марта '13 в 22:24
источник поделиться

Это делает поиск по ширине кратчайшего пути (серии взрывов) через этот "лабиринт" позиций. Нет, я не могу доказать, что алгоритма не существует быстрее, извините.

#!/usr/bin/env python

M = ((1,2,3,4),
     (2,3,4,5),
     (5,2,7,4),
     (2,3,5,8))

def eachPossibleMove(m):
  for y in range(1, len(m)-1):
    for x in range(1, len(m[0])-1):
      if (0 == m[y-1][x-1] == m[y-1][x] == m[y-1][x+1] ==
               m[y][x-1]   == m[y][x]   == m[y][x+1] ==
               m[y+1][x-1] == m[y+1][x] == m[y+1][x+1]):
        continue
      yield x, y

def bomb(m, (mx, my)):
  return tuple(tuple(max(0, m[y][x]-1)
      if mx-1 <= x <= mx+1 and my-1 <= y <= my+1
      else m[y][x]
      for x in range(len(m[y])))
    for y in range(len(m)))

def findFirstSolution(m, path=[]):
#  print path
#  print m
  if sum(map(sum, m)) == 0:  # empty?
    return path
  for move in eachPossibleMove(m):
    return findFirstSolution(bomb(m, move), path + [ move ])

def findShortestSolution(m):
  black = {}
  nextWhite = { m: [] }
  while nextWhite:
    white = nextWhite
    nextWhite = {}
    for position, path in white.iteritems():
      for move in eachPossibleMove(position):
        nextPosition = bomb(position, move)
        nextPath = path + [ move ]
        if sum(map(sum, nextPosition)) == 0:  # empty?
          return nextPath
        if nextPosition in black or nextPosition in white:
          continue  # ignore, found that one before
        nextWhite[nextPosition] = nextPath

def main(argv):
  if argv[1] == 'first':
    print findFirstSolution(M)
  elif argv[1] == 'shortest':
    print findShortestSolution(M)
  else:
    raise NotImplementedError(argv[1])

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))
3
ответ дан Alfe 09 марта '13 в 5:17
источник поделиться

Математическое целочисленное линейное программирование с использованием ветвей и границ

Как уже упоминалось, эту проблему можно решить, используя целочисленное линейное программирование (которое NP-Hard). Mathematica уже встроена в ILP. "To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method." [см. Ограниченная оптимизация Учебное пособие в Mathematica..]

Я написал следующий код, который использует библиотеки ILP Mathematica. Это удивительно быстро.

solveMatrixBombProblem[problem_, r_, c_] := 
 Module[{}, 
  bombEffect[x_, y_, m_, n_] := 
   Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
        j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
  bombMatrix[m_, n_] := 
   Transpose[
    Table[Table[
      Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
        n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
       m*n - 1}], {i, 0, m*n - 1}]];
  X := x /@ Range[c*r];
  sol = Minimize[{Total[X], 
     And @@ Thread[bombMatrix[r, c].X >= problem] && 
      And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
      Element[X, Integers]}, X];
  Print["Minimum required bombs = ", sol[[1]]];
  Print["A possible solution = ", 
   MatrixForm[
    Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
      c - 1}]]];]

В примере, указанном в задаче:

solveMatrixBombProblem[{2, 3, 4, 7, 1, 1, 5, 2, 6, 2, 4, 3, 4, 2, 1, 2, 1, 2, 4, 1, 3, 1, 3, 4, 1, 2, 1, 4, 3, 2, 6, 9, 1, 6, 4}, 7, 5]

Выходы

enter image description here

Для тех, кто читает это с жадным алгоритмом

Попробуйте выполнить свой код по следующей проблеме 10x10:

5   20  7   1   9   8   19  16  11  3  
17  8   15  17  12  4   5   16  8   18  
4   19  12  11  9   7   4   15  14  6  
17  20  4   9   19  8   17  2   10  8  
3   9   10  13  8   9   12  12  6   18  
16  16  2   10  7   12  17  11  4   15  
11  1   15  1   5   11  3   12  8   3  
7   11  16  19  17  11  20  2   5   19  
5   18  2   17  7   14  19  11  1   6  
13  20  8   4   15  10  19  5   11  12

Здесь он разделяется запятой:

5, 20, 7, 1, 9, 8, 19, 16, 11, 3, 17, 8, 15, 17, 12, 4, 5, 16, 8, 18, 4, 19, 12, 11, 9, 7, 4, 15, 14, 6, 17, 20, 4, 9, 19, 8, 17, 2, 10, 8, 3, 9, 10, 13, 8, 9, 12, 12, 6, 18, 16, 16, 2, 10, 7, 12, 17, 11, 4, 15, 11, 1, 15, 1, 5, 11, 3, 12, 8, 3, 7, 11, 16, 19, 17, 11, 20, 2, 5, 19, 5, 18, 2, 17, 7, 14, 19, 11, 1, 6, 13, 20, 8, 4, 15, 10, 19, 5, 11, 12

Для этой проблемы мое решение содержит 208 бомбы. Вот возможное решение (я смог решить это примерно через 12 секунд).

enter image description here

Как способ тестирования результатов, которые производит Mathematica, посмотрите, может ли ваш жадный алгоритм сделать что-то лучше.

2
ответ дан darksky 16 марта '13 в 10:42
источник поделиться

Вот еще одна идея:

Давайте начнем с присвоения веса каждому пространству на доске за то, сколько чисел было бы уменьшено, сбросив там бомбу. Поэтому, если пространство имеет ненулевое число, оно получает точку, и если любое смежное с ним пространство имеет ненулевое число, оно получает дополнительную точку. Поэтому, если есть сетка 1000 на 1000, мы имеем вес, присвоенный каждому из 1 миллиона пространств.

Затем отсортируйте список пробелов по весу и нанесите бомбу на самый высокий вес. Это, по-видимому, самый сильный удар для нашего доллара.

После этого обновите вес каждого места, на вес которого влияет бомба. Это будет пространство, в которое вы бомбили, и любое пространство, прилегающее к нему, и любое пространство, непосредственно прилегающее к ним. Другими словами, любое пространство, которое могло бы иметь свое значение, сведенное к нулю бомбежкой, или значение соседнего пространства, сведенного к нулю.

Затем пересоберите список пробелов по весу. Поскольку только небольшое подмножество пространств изменило их вес при бомбардировке, вам не нужно использовать весь список, просто переместите их в списке.

Убейте новое место с большим весом и повторите процедуру.

Это гарантирует, что каждая бомбардировка уменьшает как можно больше пробелов (в основном, она достигает как можно меньше пробелов), поэтому она будет оптимальной, за исключением того, что они могут быть связями в весах. Таким образом, вам может понадобиться сделать отслеживание сзади, когда есть галстук для верхнего веса. Но только галстук для верхнего веса имеет значение, хотя и не для других связей, поэтому, надеюсь, это не слишком много назад-отслеживания.

Edit: Мистический контрпример ниже демонстрирует, что на самом деле это не гарантируется оптимальным, независимо от связей в весах. В некоторых случаях уменьшение веса в максимально возможной степени на данном этапе фактически оставляет оставшиеся бомбы слишком распространенными, чтобы достичь столь же высокого кумулятивного сокращения после второго шага, как вы могли бы иметь чуть менее жадный выбор на первом этапе. Меня несколько смутило представление о том, что результаты нечувствительны к порядку взрывов. Они нечувствительны к порядку в том, что вы можете совершать любые серии взрывов и воспроизводить их с самого начала в другом порядке и заканчивать тем же результатом. Но из этого не следует, что вы можете рассматривать каждую бомбежку самостоятельно. Или, по крайней мере, каждая бомбардировка должна учитываться таким образом, чтобы учесть, насколько хорошо она устанавливает доску для последующих взрывов.

2
ответ дан Tim Goodman 08 марта '13 в 23:39
источник поделиться

Вот решение, которое обобщает хорошие свойства углов.

Предположим, что мы можем найти идеальную точку падения для данного поля, то есть лучший способ уменьшить значение в ней. Затем, чтобы найти минимальное количество удаляемых бомб, может быть первый проект алгоритма (код скопирован с помощью Ruby-реализации):

dropped_bomb_count = 0
while there_are_cells_with_non_zero_count_left
  coordinates = choose_a_perfect_drop_point
  drop_bomb(coordinates)
  dropped_bomb_count += 1
end
return dropped_bomb_count

Задача choose_a_perfect_drop_point. Во-первых, давайте определим, какова идеальная точка падения.

  • Точка падения для (x, y) уменьшает значение в (x, y). Он также может уменьшать значения в других ячейках.
  • Точка падения a для (x, y) лучше, чем точка падения b для (x, y), если она уменьшает значения в собственном супермножестве ячеек, которые b уменьшается.
  • Точка падения максимальна, если нет другой лучшей точки падения.
  • Две точки падения для (x, y) эквивалентны, если они уменьшают один и тот же набор ячеек.
  • Точка падения для (x, y) идеальна, если она эквивалентна всем максимальным точкам падения для (x, y).

Если для (x, y) есть идеальная точка падения, вы не можете более эффективно уменьшить значение (x, y), чем сбросить бомбу на одну из идеальных точек падения для (x, y).

Идеальная точка падения для данного поля является идеальной точкой падения для любой из ее ячеек.

Вот несколько примеров:

1 0 1 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

Идеальная точка падения для ячейки (0, 0) (индекс на основе нуля) (1, 1). Все остальные точки падения для (1, 1), то есть (0, 0), (0, 1) и (1, 0), уменьшают меньше клеток.

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

Идеальная точка падения для ячейки (2, 2) (индекс на основе нуля) составляет (2, 2), а также все окружающие ячейки (1, 1), (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2) и (3, 3).

0 0 0 0 1
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

идеальная точка падения для ячейки (2, 2) равна (3, 1): она уменьшает значение в (2, 2), а значение в (4, 0). Все остальные точки падения для (2, 2) не являются максимальными, поскольку они уменьшают на одну ячейку меньше. Идеальная точка падения для (2, 2) также является идеальной точкой падения для (4, 0), и это единственная совершенная точка падения для поля. Это приводит к идеальному решению для этого поля (одно падение бомбы).

1 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
1 0 0 0 0

Не существует идеальной точки падения для (2, 2): обе (1, 1) и (1, 3) уменьшают (2, 2) и другую ячейку (это максимальные точки падения для (2, 2)), но они не эквивалентны. Однако (1, 1) является идеальной точкой падения для (0, 0), а (1, 3) является идеальной точкой падения для (0, 4).

С этим определением совершенных точек падения и определенного порядка проверок, я получаю следующий результат для примера в вопросе:

Drop bomb on 1, 1
Drop bomb on 1, 1
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 6
Drop bomb on 1, 2
Drop bomb on 1, 2
Drop bomb on 0, 6
Drop bomb on 0, 6
Drop bomb on 2, 1
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 3, 1
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 4
Drop bomb on 3, 4
Drop bomb on 3, 3
Drop bomb on 3, 3
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 4, 6
28

Однако алгоритм работает только в том случае, если после каждого шага есть хотя бы одна идеальная точка падения. Можно построить примеры, в которых нет идеальных точек падения:

0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0

В этих случаях мы можем модифицировать алгоритм так, чтобы вместо идеальной точки падения выберем координату с минимальным выбором максимальных точек падения, а затем вычислим минимум для каждого выбора. В приведенном выше случае все ячейки со значениями имеют две максимальные точки падения. Например, (0, 1) имеет максимальные точки падения (1, 1) и (1, 2). Выбор одного из них, а затем вычисление минимума приводит к такому результату:

Drop bomb on 1, 1
Drop bomb on 2, 2
Drop bomb on 1, 2
Drop bomb on 2, 1
2
2
ответ дан Tammo Freese 11 марта '13 в 1:47
источник поделиться

Я считаю, что для минимизации количества бомб вам просто нужно максимально увеличить количество урона. для этого нужно проверить область, которая имеет самую сильную силу. Поэтому вы сначала анализируете поле с ядром 3x3 и проверяете, где сумма сильнее.. и бомба там.. и делайте, пока поле не будет плоским.. для это подало ответ 28

var oMatrix = [
[2,3,4,7,1],
[1,5,2,6,2],
[4,3,4,2,1],
[2,1,2,4,1],
[3,1,3,4,1],
[2,1,4,3,2],
[6,9,1,6,4]
]

var nBombs = 0;
do
{
    var bSpacesLeftToBomb = false;
    var nHigh = 0;
    var nCellX = 0;
    var nCellY = 0;
    for(var y = 1 ; y<oMatrix.length-1;y++) 
        for(var x = 1 ; x<oMatrix[y].length-1;x++)  
        {
            var nValue = 0;
            for(var yy = y-1;yy<=y+1;yy++)
                for(var xx = x-1;xx<=x+1;xx++)
                    nValue += oMatrix[yy][xx];

            if(nValue>nHigh)
            {
                nHigh = nValue;
                nCellX = x;
                nCellY = y; 
            }

        }
    if(nHigh>0)
    {
        nBombs++;

        for(var yy = nCellY-1;yy<=nCellY+1;yy++)
        {
            for(var xx = nCellX-1;xx<=nCellX+1;xx++)
            {
                if(oMatrix[yy][xx]<=0)
                    continue;
                oMatrix[yy][xx] = --oMatrix[yy][xx];
            }
        }
        bSpacesLeftToBomb = true;
    }
}
while(bSpacesLeftToBomb);

alert(nBombs+'bombs');
2
ответ дан CaldasGSM 09 марта '13 в 4:59
источник поделиться

Чтобы свести к минимуму количество бомб, мы должны максимизировать эффект каждой бомбы. Чтобы добиться этого, на каждом шаге мы должны выбрать лучшую цель. Для каждой точки суммирования ее и ее восьми соседей можно было бы использовать как количество эффективности бомбардировки этой точки. Это обеспечит оптимальную последовательность бомб.

UPD. Мы также должны учитывать количество нулей, потому что они неэффективны. На самом деле проблема состоит в том, чтобы свести к минимуму количество удаленных нулей. Но мы не можем знать, как любой шаг приближает нас к этой цели. Я согласен с идеей, что проблема NP-полная. Я прихожу к жадному подходу, который даст ответ близко к реальному.

2
ответ дан Mikhail 09 марта '13 в 21:30
источник поделиться

функция оценки, общая сумма:

int f (int ** matrix, int width, int height, int x, int y)
{
    int m[3][3] = { 0 };

    m[1][1] = matrix[x][y];
    if (x > 0) m[0][1] = matrix[x-1][y];
    if (x < width-1) m[2][1] = matrix[x+1][y];

    if (y > 0)
    {
        m[1][0] = matrix[x][y-1];
        if (x > 0) m[0][0] = matrix[x-1][y-1];
        if (x < width-1) m[2][0] = matrix[x+1][y-1];
    }

    if (y < height-1)
    {
        m[1][2] = matrix[x][y+1];
        if (x > 0) m[0][2] = matrix[x-1][y+1];
        if (x < width-1) m[2][2] = matrix[x+1][y+1];
    }

    return m[0][0]+m[0][1]+m[0][2]+m[1][0]+m[1][1]+m[1][2]+m[2][0]+m[2][1]+m[2][2];
}

целевая функция:

Point bestState (int ** matrix, int width, int height)
{
    Point p = new Point(0,0);
    int bestScore = 0;
    int b = 0;

    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
        {
            b = f(matrix,width,height,i,j);

            if (b > bestScore)
            {
                bestScore = best;
                p = new Point(i,j);
            }
        }

    retunr p;
}

уничтожить функцию:

void destroy (int ** matrix, int width, int height, Point p)
{
    int x = p.x;
    int y = p.y;

    if(matrix[x][y] > 0) matrix[x][y]--;
    if (x > 0) if(matrix[x-1][y] > 0) matrix[x-1][y]--;
    if (x < width-1) if(matrix[x+1][y] > 0) matrix[x+1][y]--;

    if (y > 0)
    {
        if(matrix[x][y-1] > 0) matrix[x][y-1]--;
        if (x > 0) if(matrix[x-1][y-1] > 0) matrix[x-1][y-1]--;
        if (x < width-1) if(matrix[x+1][y-1] > 0) matrix[x+1][y-1]--;
    }

    if (y < height-1)
    {
        if(matrix[x][y] > 0) matrix[x][y+1]--;
        if (x > 0) if(matrix[x-1][y+1] > 0) matrix[x-1][y+1]--;
        if (x < width-1) if(matrix[x+1][y+1] > 0) matrix[x+1][y+1]--;
    }
}

целевая функция:

bool isGoal (int ** matrix, int width, int height)
{
    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
            if (matrix[i][j] > 0)
                return false;
    return true;
}

функция линейной максимизации:

void solve (int ** matrix, int width, int height)
{
    while (!isGoal(matrix,width,height))
    {
        destroy(matrix,width,height, bestState(matrix,width,height));
    }
}

Это не оптимально, но может быть оптимизировано путем поиска лучшей оценочной функции.

.. но, думая об этой проблеме, я думал, что одной из главных проблем является то, что в какой-то момент в середине нулей появляются заброшенные фигуры, поэтому я бы взял другой подход... который доминирует над минимальными значениями в ноль, затем попытайтесь избежать нулей, насколько это возможно, что приведет к общей минимизации минимального существующего значения (ов) или так

1
ответ дан Khaled.K 13 марта '13 в 1:56
источник поделиться

У меня тоже было 28 ходов. Я использовал два теста для лучшего следующего шага: сначала шаг, создающий минимальную сумму для доски. Во-вторых, для равных сумм движение, создающее максимальную плотность, определяется как:

number-of-zeros / number-of-groups-of-zeros

Это Хаскелл. "solve board" показывает решение для двигателя. Вы можете играть в игру, набрав "main", затем введите целевую точку, "лучше" для рекомендации или "выйти", чтобы выйти.

ВЫВОД:
* Главная > Решить панель
[(4,4), (3,6), (3,3), (2,2), (2,2), (4,6), (4,6), (2,6), ( 3,2), (4,2), (2,6), (3,3), (4,3), (2,6), (4,2), (4,6), (4, 6), (3,6), (2,6), (2,6), (2,4), (2,4), (2,6), (3,6), (4,2), (4,2), (4,2), (4,2)]

import Data.List
import Data.List.Split
import Data.Ord
import Data.Function(on)

board = [2,3,4,7,1,
         1,5,2,6,2,
         4,3,4,2,1,
         2,1,2,4,1,
         3,1,3,4,1,
         2,1,4,3,2,
         6,9,1,6,4]

n = 5
m = 7

updateBoard board pt =
  let x = fst pt
      y = snd pt
      precedingLines = replicate ((y-2) * n) 0
      bomb = concat $ replicate (if y == 1
                                    then 2
                                    else min 3 (m+2-y)) (replicate (x-2) 0 
                                                         ++ (if x == 1 
                                                                then [1,1]
                                                                else replicate (min 3 (n+2-x)) 1)
                                                                ++ replicate (n-(x+1)) 0)
  in zipWith (\a b -> max 0 (a-b)) board (precedingLines ++ bomb ++ repeat 0)

showBoard board = 
  let top = "   " ++ (concat $ map (\x -> show x ++ ".") [1..n]) ++ "\n"
      chunks = chunksOf n board
  in putStrLn (top ++ showBoard' chunks "" 1)
       where showBoard' []     str count = str
             showBoard' (x:xs) str count =
               showBoard' xs (str ++ show count ++ "." ++ show x ++ "\n") (count+1)

instances _ [] = 0
instances x (y:ys)
  | x == y    = 1 + instances x ys
  | otherwise = instances x ys

density a = 
  let numZeros = instances 0 a
      groupsOfZeros = filter (\x -> head x == 0) (group a)
  in if null groupsOfZeros then 0 else numZeros / fromIntegral (length groupsOfZeros)

boardDensity board = sum (map density (chunksOf n board))

moves = [(a,b) | a <- [2..n-1], b <- [2..m-1]]               

bestMove board = 
  let lowestSumMoves = take 1 $ groupBy ((==) `on` snd) 
                              $ sortBy (comparing snd) (map (\x -> (x, sum $ updateBoard board x)) (moves))
  in if null lowestSumMoves
        then (0,0)
        else let lowestSumMoves' = map (\x -> fst x) (head lowestSumMoves) 
             in fst $ head $ reverse $ sortBy (comparing snd) 
                (map (\x -> (x, boardDensity $ updateBoard board x)) (lowestSumMoves'))   

solve board = solve' board [] where
  solve' board result
    | sum board == 0 = result
    | otherwise      = 
        let best = bestMove board 
        in solve' (updateBoard board best) (result ++ [best])

main :: IO ()
main = mainLoop board where
  mainLoop board = do 
    putStrLn ""
    showBoard board
    putStr "Pt: "
    a <- getLine
    case a of 
      "quit"    -> do putStrLn ""
                      return ()
      "best"    -> do putStrLn (show $ bestMove board)
                      mainLoop board
      otherwise -> let ws = splitOn "," a
                       pt = (read (head ws), read (last ws))
                   in do mainLoop (updateBoard board pt)
1
ответ дан גלעד ברקן 10 марта '13 в 1:14
источник поделиться

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

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

2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))

значение ячейки +1 для каждой смежной ячейки со значением, превышающим 0

1
ответ дан cosmin.danisor 08 марта '13 в 21:00
источник поделиться

Здесь мое решение.. Я не буду писать его в коде еще с тех пор, как у меня нет времени, но я считаю, что это должно приводить к оптимальному количеству ходов каждый раз - хотя я не уверен, насколько он эффективен найти бомбы.

Во-первых, как сказал @Luka Rahne в одном из комментариев, порядок, в котором вы бомбите, не имеет значения - только комбинация.

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

Это создает основу для моей версии алгоритма: Мы можем бомбить "1-офф от углов" сначала или в последнюю очередь, это не имеет значения (теоретически) Мы бомбили эти первые, потому что это облегчает принятие решений (на практике) Мы бомбим точку, которая влияет на большинство точек, одновременно бомбардируя эти углы.

Определите Points of Resistance как точки на доске с большинством не подлежащих бомбардировке точек + наибольшее количество 0 вокруг них

не подлежащие бомбардировке точки могут быть определены как точки, которые не существуют в нашей текущей области панели, на которую мы смотрим.

Я также определяю 4 границы, которые будут обрабатывать нашу область: Top = 0, Left = 0, Bottom = k, right = j. (значения для запуска)

Наконец, я определяю оптимальные бомбы как бомбы, которые падают на точки, прилегающие к точкам сопротивления, и касаются (1) наивысшей точки сопротивления и (2) наибольшей количество возможных точек.

Что касается подхода - очевидно, что мы работаем извне. Мы сможем одновременно работать с 4-мя бомбардировщиками.

Первые точки сопротивления, очевидно, являются нашими углами. Точки "вне границ" не подвергаются бомбардировке (на каждый угол есть 5 точек). Таким образом, мы сначала бомбили точки по диагонали с углов.

Алгоритм:

  • Найдите 4 оптимальных точки бомбы.
  • Если точка бомбы бомбит точку сопротивления, касающуюся 2-х границ (т.е. угла), бомба до этой точки равна 0. В противном случае бомбить каждую, пока одна из точек сопротивления, касающаяся оптимальной точки бомбы, не станет 0.
  • для каждой границы: if (сумма (связанная) == 0) предварительная граница

повторить, пока TOP = BOTTOM и LEFT = RIGHT

Я попытаюсь записать фактический код позже

1
ответ дан Etai 09 марта '13 в 19:26
источник поделиться

Вы можете использовать планирование пространства состояний. Например, используя A * (или один из его вариантов) в сочетании с эвристическим f = g + h следующим образом:

  • g: количество бомб, упавших до сих пор
  • h: сумма по всем значениям сетки, деленная на 9 (что является лучшим результатом, что означает допустимую эвристику)
1
ответ дан キキジキ 10 марта '13 в 6:52
источник поделиться

Ну, предположим, что мы числим правые позиции 1, 2,..., n x m. Любая последовательность капель бомб может быть представлена ​​последовательностью чисел в этом наборе, где числа могут повторяться. Тем не менее, эффект на доске тот же, независимо от того, в каком порядке вы бросаете бомбы, поэтому действительно любой выбор капель бомб может быть представлен как список номеров nxm, где первое число представляет количество бомб, сброшенных на позицию 1, второе число представляет количество бомб, сброшенных в положение 2, и т.д. Позвольте называть этот список nxm номерами "ключ".

Вы можете попробовать сначала вычислить все состояния платы, возникающие в результате 1-го падения бомбы, а затем использовать их для расчета всех состояний платы в результате 2-х бонусных капель и т.д., пока не получите все нули. Но на каждом шаге вы будете кэшировать состояния с помощью ключа, указанного выше, поэтому вы можете использовать эти результаты при вычислении следующего шага (подход "динамического программирования" ).

Но в зависимости от размера n, m и чисел в сетке требования к памяти этого подхода могут быть чрезмерными. Вы можете выбросить все результаты за N выстрелов, как только вы рассчитали все результаты для N + 1, так что там есть некоторые сбережения. И, конечно же, вы не можете кэшировать что-либо за счет того, что это займет намного больше времени - подход к динамическому программированию торгует памятью для скорости.

1
ответ дан Tim Goodman 08 марта '13 в 21:05
источник поделиться
  • Никогда не бомбить границу (если у квадрата нет соседнего соседа)
  • Нулевой угол.
  • К нулевому углу, падение значения угловой одной квадратной диагонали (единственный соседний соседний)
  • Это создаст новые углы. Перейдите к 2

Редактировать: не заметил, что Костек предложил почти такой же подход, так что теперь я делаю более сильное требование: Если углы для очистки выбраны так, чтобы быть всегда на внешнем слое, тогда это оптимально.

В примере OP: сброс 2 (как 1 + 1 или 2) на что-либо еще, чем на 5, не приводит к удару по любому квадрату, который выпадет на 5. Поэтому мы просто должны сбросить 2 на 5 (и 6 на левый нижний 1...)

После этого есть только один способ, как очистить (в верхнем левом) углу то, что было оригинально 1 (теперь 0), а это - сбросить 0 на B3 (отличить как обозначение). И так далее.

Только после очистки целых столбцов A и E и 1 и 7 строк начните очищать один слой глубже.

Рассмотрите очищенные только те, умышленно очищенные, очищающие 0 углов значения ничего не стоят и упрощают думать об этом.

Поскольку все бомбы, сброшенные таким образом, должны быть сброшены, и это приводит к очищенным полям, это оптимальное решение.


После хорошего сна я понял, что это неправда. Рассмотрим

  ABCDE    
1 01000
2 10000
3 00000
4 00000

Мой подход сбрасывал бы бомбы на B3 и C2, когда падение на B2 было бы достаточно

1
ответ дан Alpedar 08 марта '13 в 21:17
источник поделиться

Я не могу придумать способ рассчитать фактическое число, не просто вычислив кампанию бомбардировки, используя мою лучшую эвристику, и надеюсь, что я получу разумный результат.

Итак, мой метод - вычислять метрику эффективности бомбардировки для каждой ячейки, бомбить клетку с наивысшим значением,... итерировать процесс, пока я не сплющил все. Некоторые из них выступают за использование простого потенциального ущерба (т.е. Оценка от 0 до 9) в качестве показателя, но это не оправдывает себя, забирая клетки с высоким значением и не применяя наложение повреждений. Я бы вычислил cell value - sum of all neighbouring cells, reset любое положительное значение 0 и использовал абсолютное значение чего-либо отрицательного. Интуитивно эта метрика должна сделать выбор, который поможет максимизировать наложение повреждений на ячейки с большим количеством отсчетов, а не нарезать их непосредственно.

Код ниже достигает полного разрушения тестового поля в 28 бомбах (обратите внимание, что использование потенциального урона как метрики дает 31!).

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
  internal class Program
  {
    // store the battle field as flat array + dimensions
    private static int _width = 5;
    private static int _length = 7;
    private static int[] _field = new int[] {
        2, 3, 4, 7, 1,
        1, 5, 2, 6, 2,
        4, 3, 4, 2, 1,
        2, 1, 2, 4, 1,
        3, 1, 3, 4, 1,
        2, 1, 4, 3, 2,
        6, 9, 1, 6, 4
    };
    // this will store the devastation metric
    private static int[] _metric;

    // do the work
    private static void Main(string[] args)
    {
        int count = 0;

        while (_field.Sum() > 0)
        {
            Console.Out.WriteLine("Round {0}:", ++count);
            GetBlastPotential();
            int cell_to_bomb = FindBestBombingSite();
            PrintField(cell_to_bomb);
            Bomb(cell_to_bomb);
        }
        Console.Out.WriteLine("Done in {0} rounds", count);
    } 

    // convert 2D position to 1D index
    private static int Get1DCoord(int x, int y)
    {
        if ((x < 0) || (y < 0) || (x >= _width) || (y >= _length)) return -1;
        else
        {
            return (y * _width) + x;
        }
    }

    // Convert 1D index to 2D position
    private static void Get2DCoord(int n, out int x, out int y)
    {
        if ((n < 0) || (n >= _field.Length))
        {
            x = -1;
            y = -1;
        }
        else
        {
            x = n % _width;
            y = n / _width;
        }
    }

    // Compute a list of 1D indices for a cell neighbours
    private static List<int> GetNeighbours(int cell)
    {
        List<int> neighbours = new List<int>();
        int x, y;
        Get2DCoord(cell, out x, out y);
        if ((x >= 0) && (y >= 0))
        {
            List<int> tmp = new List<int>();
            tmp.Add(Get1DCoord(x - 1, y - 1));
            tmp.Add(Get1DCoord(x - 1, y));
            tmp.Add(Get1DCoord(x - 1, y + 1));
            tmp.Add(Get1DCoord(x, y - 1));
            tmp.Add(Get1DCoord(x, y + 1));
            tmp.Add(Get1DCoord(x + 1, y - 1));
            tmp.Add(Get1DCoord(x + 1, y));
            tmp.Add(Get1DCoord(x + 1, y + 1));

            // eliminate invalid coords - i.e. stuff past the edges
            foreach (int c in tmp) if (c >= 0) neighbours.Add(c);
        }
        return neighbours;
    }

    // Compute the devastation metric for each cell
    // Represent the Value of the cell minus the sum of all its neighbours
    private static void GetBlastPotential()
    {
        _metric = new int[_field.Length];
        for (int i = 0; i < _field.Length; i++)
        {
            _metric[i] = _field[i];
            List<int> neighbours = GetNeighbours(i);
            if (neighbours != null)
            {
                foreach (int j in neighbours) _metric[i] -= _field[j];
            }
        }
        for (int i = 0; i < _metric.Length; i++)
        {
            _metric[i] = (_metric[i] < 0) ? Math.Abs(_metric[i]) : 0;
        }
    }

    //// Compute the simple expected damage a bomb would score
    //private static void GetBlastPotential()
    //{
    //    _metric = new int[_field.Length];
    //    for (int i = 0; i < _field.Length; i++)
    //    {
    //        _metric[i] = (_field[i] > 0) ? 1 : 0;
    //        List<int> neighbours = GetNeighbours(i);
    //        if (neighbours != null)
    //        {
    //            foreach (int j in neighbours) _metric[i] += (_field[j] > 0) ? 1 : 0;
    //        }
    //    }            
    //}

    // Update the battle field upon dropping a bomb
    private static void Bomb(int cell)
    {
        List<int> neighbours = GetNeighbours(cell);
        foreach (int i in neighbours)
        {
            if (_field[i] > 0) _field[i]--;
        }
    }

    // Find the best bombing site - just return index of local maxima
    private static int FindBestBombingSite()
    {
        int max_idx = 0;
        int max_val = int.MinValue;
        for (int i = 0; i < _metric.Length; i++)
        {
            if (_metric[i] > max_val)
            {
                max_val = _metric[i];
                max_idx = i;
            }
        }
        return max_idx;
    }

    // Display the battle field on the console
    private static void PrintField(int cell)
    {
        for (int x = 0; x < _width; x++)
        {
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _field[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _field[c]).PadLeft(4));
            }
            Console.Out.Write(" || ");
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _metric[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _metric[c]).PadLeft(4));
            }
            Console.Out.WriteLine();
        }
        Console.Out.WriteLine();
    }           
  }
}

Образующийся шаблон бомбардировки выводится следующим образом (значения поля слева, метрика справа)

Round 1:
  2   1   4   2   3   2   6  ||   7  16   8  10   4  18   6
  3   5   3   1   1   1   9  ||  11  18  18  21  17  28   5
  4  [2]  4   2   3   4   1  ||  19 [32] 21  20  17  24  22
  7   6   2   4   4   3   6  ||   8  17  20  14  16  22   8
  1   2   1   1   1   2   4  ||  14  15  14  11  13  16   7

Round 2:
  2   1   4   2   3   2   6  ||   5  13   6   9   4  18   6
  2   4   2   1   1  [1]  9  ||  10  15  17  19  17 [28]  5
  3   2   3   2   3   4   1  ||  16  24  18  17  17  24  22
  6   5   1   4   4   3   6  ||   7  14  19  12  16  22   8
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 3:
  2   1   4   2   2   1   5  ||   5  13   6   7   3  15   5
  2   4   2   1   0   1   8  ||  10  15  17  16  14  20   2
  3  [2]  3   2   2   3   0  ||  16 [24] 18  15  16  21  21
  6   5   1   4   4   3   6  ||   7  14  19  11  14  19   6
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 4:
  2   1   4   2   2   1   5  ||   3  10   4   6   3  15   5
  1   3   1   1   0   1   8  ||   9  12  16  14  14  20   2
  2   2   2   2   2  [3]  0  ||  13  16  15  12  16 [21] 21
  5   4   0   4   4   3   6  ||   6  11  18   9  14  19   6
  1   2   1   1   1   2   4  ||  10   9  10   9  13  16   7

Round 5:
  2   1   4   2   2   1   5  ||   3  10   4   6   2  13   3
  1   3   1   1   0  [0]  7  ||   9  12  16  13  12 [19]  2
  2   2   2   2   1   3   0  ||  13  16  15  10  14  15  17
  5   4   0   4   3   2   5  ||   6  11  18   7  13  17   6
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 6:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   9  12  16  11   8  13   0
  2   2   2   2   0   2   0  ||  13  16  15   9  14  14  15
  5   4  [0]  4   3   2   5  ||   6  11 [18]  6  11  15   5
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 7:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   8  10  13   9   7  13   0
  2  [1]  1   1   0   2   0  ||  11 [15] 12   8  12  14  15
  5   3   0   3   3   2   5  ||   3   8  10   3   8  15   5
  1   1   0   0   1   2   4  ||   8   8   7   7   9  13   5

Round 8:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   7  13   0
  1   1   0   1   0   2   0  ||   8   8  10   6  12  14  15
  4   2   0   3   3  [2]  5  ||   2   6   8   2   8 [15]  5
  1   1   0   0   1   2   4  ||   6   6   6   7   9  13   5

Round 9:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   6  12   0
  1   1   0   1   0  [1]  0  ||   8   8  10   5  10 [13] 13
  4   2   0   3   2   2   4  ||   2   6   8   0   6   9   3
  1   1   0   0   0   1   3  ||   6   6   6   5   8  10   4

Round 10:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  10   1
  0   2  [0]  1   0   0   5  ||   7   7 [12]  7   6  11   0
  1   1   0   1   0   1   0  ||   8   8  10   4   8   9  10
  4   2   0   3   1   1   3  ||   2   6   8   0   6   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 11:
  2   0   3   1   1   0   4  ||   0   6   0   3   0  10   1
  0   1   0   0   0  [0]  5  ||   4   5   5   5   3 [11]  0
  1   0   0   0   0   1   0  ||   6   8   6   4   6   9  10
  4   2   0   3   1   1   3  ||   1   5   6   0   5   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 12:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   7   1
  0   1   0   0   0   0   4  ||   4   5   5   4   1   7   0
  1   0   0   0   0  [0]  0  ||   6   8   6   4   5  [9]  8
  4   2   0   3   1   1   3  ||   1   5   6   0   4   7   2
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 13:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   6   0
  0   1   0   0   0   0   3  ||   4   5   5   4   1   6   0
  1  [0]  0   0   0   0   0  ||   6  [8]  6   3   3   5   5
  4   2   0   3   0   0   2  ||   1   5   6   0   4   6   2
  1   1   0   0   0   1   3  ||   6   6   6   3   4   4   0

Round 14:
  2   0   3   1   0  [0]  3  ||   0   5   0   2   1  [6]  0
  0   0   0   0   0   0   3  ||   2   5   4   4   1   6   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   5   5
  3   1   0   3   0   0   2  ||   0   4   5   0   4   6   2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 15:
  2   0   3   1   0   0   2  ||   0   5   0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   4   4
  3   1   0   3   0  [0]  2  ||   0   4   5   0   4  [6]  2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 16:
  2  [0]  3   1   0   0   2  ||   0  [5]  0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1   0   3   0   0   1  ||   0   4   5   0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 17:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1  [0]  3   0   0   1  ||   0   4  [5]  0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 18:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   3   3   2   2   2   3   3
  3  [0]  0   2   0   0   1  ||   0  [4]  2   0   2   3   1
  1   0   0   0   0   0   2  ||   2   4   2   2   2   3   0

Round 19:
  1   0   2   1   0  [0]  2  ||   0   3   0   1   1  [4]  0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   3   3
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 20:
  1  [0]  2   1   0   0   1  ||   0  [3]  0   1   1   2   0
  0   0   0   0   0   0   1  ||   1   3   3   3   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 21:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0  [0]  1  ||   0   2   2   0   2  [3]  1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 22:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
 [0]  0   0   0   0   0   0  ||  [2]  2   2   2   2   1   1
  2   0   0   2   0   0   0  ||   0   2   2   0   2   1   1
  0   0   0   0   0   0   1  ||   2   2   2   2   2   1   0

Round 23:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0  [0]  0   0   0   1  ||   0   1  [2]  2   1   2   0
  0   0   0   0   0   0   0  ||   1   1   2   2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 24:
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0  [0]  0   0   0   0  ||   1   1  [2]  2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 25:
  0   0   0   0   0  [0]  1  ||   0   0   0   0   0  [2]  0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   0  ||   1   1   1   1   1   1   1
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 26:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
 [0]  0   0   0   0   0   0  ||  [1]  1   1   1   1   0   0
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 27:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0  [0]  0   0   0   0  ||   0   0  [1]  1   1   0   0
  0   0   0   1   0   0   0  ||   0   0   1   0   1   1   1
  0   0   0   0   0   0   1  ||   0   0   1   1   1   1   0

Round 28:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0  [0]  0  ||   0   0   0   0   0  [1]  1
  0   0   0   0   0   0   1  ||   0   0   0   0   0   1   0

Done in 28 rounds
1
ответ дан zeFrenchy 12 марта '13 в 13:01
источник поделиться

Грубая сила!

Я знаю, что это не эффективно, но даже если вы найдете более быстрый алгоритм, вы всегда можете проверить этот результат, чтобы узнать, насколько он точным.

Используйте некоторую рекурсию, например:

void fn(tableState ts, currentlevel cl)
{
  // first check if ts is all zeros yet, if not:
  //
  // do a for loop to go through all cells of ts, 
  // for each cell do a bomb, and then
  // call: 
  // fn(ts, cl + 1);

}

Вы можете сделать это более эффективным путем кеширования, если другой способ приведет к такому же результату, вы не должны повторять те же шаги.

Разработать:

если бомбардировочная ячейка 1,3,5 приводит к такому же результату, что и камера бомбардировщика 5,3,1, то вы не должны повторно делать все следующие шаги снова для обоих случаев, только 1 достаточно, вы должны хранить где-нибудь все состояния таблицы и использовать ее результаты.

Для быстрого сравнения может использоваться хэш таблицы.

1
ответ дан sharp12345 09 марта '13 в 3:48
источник поделиться

Это можно решить, используя дерево глубины O (3 ^ (n)). Где n - сумма всех квадратов.

Сначала рассмотрим, что тривиально решить проблему с деревом O (9 ^ n), просто рассмотрим все возможные места бомбежек. Для примера см. Alfe-реализация.

Затем поймите, что мы можем работать, чтобы бомбить снизу вверх и по-прежнему получить минимальный шаблон бомбардировки.

  • Начните с нижнего левого угла.
  • Бомба забвение с единственными играми, которые имеют смысл (вверх и вправо).
  • Переместите один квадрат вправо.
  • Пока цель имеет значение больше нуля, рассмотрите каждую из двух пьес, которые имеют смысл (прямо вверх или вверх и вправо), уменьшите значение цели на единицу и создайте новую ветку для каждой возможности.
  • Переместите другое вправо.
  • В то время как цель имеет значение больше нуля, рассмотрите каждую из трех игр, которые имеют смысл (вверх по левому краю, вверх и вверх направо), уменьшите значение цели на единицу и создайте новую ветку для каждой возможности.
  • Повторяйте шаги 5 и 6, пока строка не будет удалена.
  • Поднимите строку и повторите шаги с 1 по 7, пока головоломка не будет решена.

Этот алгоритм корректен, потому что

  • В каждую точку необходимо завершить каждую строку.
  • Завершение строки всегда требует воспроизведения либо выше, либо ниже, либо внутри этой строки.
  • Всегда лучше или лучше выбирать игру, расположенную над самой низкой нечистой строкой, чем игра в строке или ниже строки.

На практике этот алгоритм будет регулярно лучше, чем его теоретический максимум, потому что он будет регулярно бомбить соседей и уменьшать размер поиска. Если предположить, что каждая бомбардировка уменьшает значение 4 дополнительных целей, то наш алгоритм будет работать в O (3 ^ (n/4)) или приблизительно O (1.3 ^ n).

Поскольку этот алгоритм все еще экспоненциальен, было бы разумно ограничить глубину поиска. Мы могли бы ограничить число ветвей, разрешенных некоторым числом, X, и как только мы это сделаем, мы вынудим алгоритм выбрать наилучший путь, который он определил до сих пор (тот, который имеет минимальную общую сумму платы в одном из своих терминальных листов). Тогда наш алгоритм гарантированно работает в O (3 ^ X) времени, но не гарантируется получение правильного ответа. Тем не менее, мы всегда можем увеличить X и проверить эмпирически, если стоит обмен между увеличенными вычислениями и лучшими ответами.

1
ответ дан Ben Haley 12 марта '13 в 8:17
источник поделиться

Здесь, похоже, существует непартийная согласующая субструктура. Рассмотрим следующий пример:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

Оптимальное решение для этого случая имеет размер 5, так как размер минимального покрытия вершин 9-цикла по его ребрам.

Этот случай, в частности, показывает, что релаксация линейного программирования, опубликованная несколькими людьми, не является точной, не работает и все эти плохие вещи. Я почти уверен, что смогу уменьшить "покрыть вершины моего планарного кубического графа как можно меньшим количеством краев" до вашей проблемы, что заставляет меня сомневаться в том, что будут работать какие-либо из жадных/холмистых решений.

Я не вижу способа решить это в полиномиальное время в худшем случае. Может быть очень умное решение для двоичного поиска и DP, которое я не вижу.

РЕДАКТИРОВАТЬ. Я вижу, что конкурс (http://deadline24.pl) является языковым агностиком; они отправляют вам кучу входных файлов, и вы отправляете их. Поэтому вам не нужно что-то, что работает в худшем случае полиномиального времени. В частности, вы можете посмотреть на ввод!

На входе есть куча небольших случаев. Тогда есть случай 10x1000, случай 100x100 и случай 1000x1000. Три больших случая - все очень хорошо. Горизонтально смежные записи обычно имеют одинаковое значение. На относительно мускулистой машине я могу решить все дела путем принудительного использования с помощью CPLEX всего за пару минут. Мне повезло на 1000х1000; релаксация LP имеет интегральное оптимальное решение. Мои решения согласуются с файлами .ans, содержащимися в комплекте тестовых данных.

Держу пари, что вы можете использовать структуру ввода гораздо более прямым способом, чем я, если бы вы взглянули на него; кажется, что вы можете просто отложить первый ряд, или два, или три раза, пока у вас ничего не останется. (Похоже, в 1000x1000 все строки не увеличиваются? Я предполагаю, что откуда ваша "часть B"?)

1
ответ дан tmyklebu 11 марта '13 в 3:21
источник поделиться

Несколько ответов пока дают экспоненциальное время, некоторые из них связаны с динамическим программированием. Я сомневаюсь, что это необходимо.

Мое решение O (mnS), где m, n - размеры платы, S - сумма всех целых чисел. Идея довольно грубая: найдите место, которое может убивать больше всего каждый раз и заканчиваться на 0.

Он дает 28 ходов для заданной платы, а также выводит плату после каждой капли.

Полный, понятный код:

import java.util.Arrays;

public class BombMinDrops {

    private static final int[][] BOARD = {{2,3,4,7,1}, {1,5,2,6,2}, {4,3,4,2,1}, {2,1,2,4,1}, {3,1,3,4,1}, {2,1,4,3,2}, {6,9,1,6,4}};
    private static final int ROWS = BOARD.length;
    private static final int COLS = BOARD[0].length;
    private static int remaining = 0;
    private static int dropCount = 0;
    static {
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                remaining = remaining + BOARD[i][j];
            }
        }
    }

    private static class Point {
        int x, y;
        int kills;

        Point(int x, int y, int kills) {
            this.x = x;
            this.y = y;
            this.kills = kills;
        }

        @Override
        public String toString() {
            return dropCount + "th drop at [" + x + ", " + y + "] , killed " + kills;
        }
    }

    private static int countPossibleKills(int x, int y) {
        int count = 0;
        for (int row = x - 1; row <= x + 1; row++) {
            for (int col = y - 1; col <= y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) count++;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        return count;
    }

    private static void drop(Point here) {
        for (int row = here.x - 1; row <= here.x + 1; row++) {
            for (int col = here.y - 1; col <= here.y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) BOARD[row][col]--;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        dropCount++;
        remaining = remaining - here.kills;
        print(here);
    }

    public static void solve() {
        while (remaining > 0) {
            Point dropWithMaxKills = new Point(-1, -1, -1);
            for (int i = 0; i < ROWS; i++) {
                for (int j = 0; j < COLS; j++) {
                    int possibleKills = countPossibleKills(i, j);
                    if (possibleKills > dropWithMaxKills.kills) {
                        dropWithMaxKills = new Point(i, j, possibleKills);
                    }
                }
            }

            drop(dropWithMaxKills);
        }

        System.out.println("Total dropped: " + dropCount);
    }

    private static void print(Point drop) {
        System.out.println(drop.toString());
        for (int[] row : BOARD) {
            System.out.println(Arrays.toString(row));
        }

        System.out.println();
    }

    public static void main(String[] args) {
        solve();
    }

}
0
ответ дан PoweredByRice 08 июня '14 в 3:00
источник поделиться

Самый медленный, но самый простой и безошибочный алгоритм генерирует и проверяет все допустимые возможности. для этого случая очень просто (потому что результат не зависит от порядка размещения бомбы).

  • создать функцию, которая N раз применяет bomp
  • создать цикл для всех возможностей размещения bompb-placement/bomb-count (остановить при матрице == 0)
  • всегда помните оптимальное решение.
  • В конце цикла у вас есть лучшее решение
    • не только количество бомб, но и их размещение

код может выглядеть так:

void copy(int **A,int **B,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
       A[i][j]=B[i][j];
    }

bool is_zero(int **M,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
      if (M[i][j]) return 0;
    return 1;
    }

void drop_bomb(int **M,int m,int n,int i,int j,int N)
    {
    int ii,jj;
    ii=i-1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    }

void solve_problem(int **M,int m,int n)
    {
    int i,j,k,max=0;
    // you probably will need to allocate matrices P,TP,TM yourself instead of this:
    int P[m][n],min;             // solution: placement,min bomb count
    int TM[m][n],TP[m][n],cnt;   // temp
    for (i=0;i<m;i++)            // max count of bomb necessary to test
     for (j=0;j<n;j++)
      if (max<M[i][j]) max=M[i][j];
    for (i=0;i<m;i++)            // reset solution
     for (j=0;j<n;j++)
      P[i][j]=max;
    min=m*n*max; 
        copy(TP,P,m,n); cnt=min;

    for (;;)  // generate all possibilities
        {
        copy(TM,M,m,n);
        for (i=0;i<m;i++)   // test solution
         for (j=0;j<n;j++)
          drop_bomb(TM,m,n,TP[i][j]);
        if (is_zero(TM,m,n))// is solution
         if (min>cnt)       // is better solution -> store it
            {
            copy(P,TP,m,n); 
            min=cnt;    
            }
        // go to next possibility
        for (i=0,j=0;;)
            {
            TP[i][j]--;
            if (TP[i][j]>=0) break;
            TP[i][j]=max;
                 i++; if (i<m) break;
            i=0; j++; if (j<n) break;
            break;
            }
        if (is_zero(TP,m,n)) break;
        }
    //result is in P,min
    }

это можно оптимизировать множеством способов,... проще всего решить reset решение с матрицей M, но вам нужно изменить максимальное значение, а также код сокращения [] []

0
ответ дан Spektre 06 сент. '13 в 12:49
источник поделиться
  • 1
  • 2

Другие вопросы по меткам