Как быстро найти оптимальную зону сброса бомбы?

Мне была сделана домашняя работа, которая выглядит примерно так:

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

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

(например, r = 10, e = 1, a = -2)

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

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

Здесь пример обрезанного, измененного размера и с измененными цветами для улучшения читаемости:

Example four-colored image

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

Что я уже разработал

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

args Map, R, A, E

for (every Soldier)
    create a Heightmap with dimensions of Map
    zero-fill the Heightmap
    on the Heightmap draw a filled circle of value 1 around Soldier with radius R

    if (Soldier is Ally)
        multiply Heightmap by A
    else
        multiply Heightmap by E

add all Heightmaps together
return coordinates of highest point in TotalHeightmap

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

Без ограничений кругов я бы сделал так:

run the above code to get a TotalHeightmap
create empty PointList

for (every Point in TotalHeightmap)
    create PointObject with properties:
        Coordinates,
        Height,
        WallsFlag = False
    add PointObject to PointList

sort PointList by descending Height

until (PointList[0].WallsFlag == True)
    for (every Soldier in radius R from PointList[0])
        if (Bresenham line connecting Soldier with PointList[0] intersects a Wall)
            subtract (A if Soldier is Ally else E) from PointList[0].Height

    set PointList[0].WallsFlag = True
    sort PointList by descending Height

return PointList[0].Coordinates

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

Я ищу более элегантное и быстрое решение этой проблемы. Как бы вы решили? Я буду реализовывать его в Python с PIL, если это поможет.

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


Ответ 1

Вот частичный ответ, который, я надеюсь, вызовет некоторое обсуждение:

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

Что было бы хорошим решением, если бы не было стен?

и далее уменьшите это до

Что было бы хорошим решением, если бы не было стен или врагов?

и даже дальше,

Что было бы хорошим решением, если бы не было ни стен, ни врагов, а радиус бомбы был 1?

что эквивалентно утверждению

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

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

В этом вопросе принятый ответ делает важное замечание: если у нас есть диск, который покрывает максимальное количество точек, мы можем перенести этот диск на другой диск с не менее чем двумя точками на его краю. Таким образом, если мы возьмем каждую пару точек на расстоянии 2 друг друга и построим два единичных окружности через эту пару точек (для O (n ^ 2) кругов в целом), то в одном из этих кругов гарантировано будет максимальное число возможно.

Это можно легко адаптировать к версии вашей "без стен" вашей проблемы. Хотя это будет O (n ^ 3) наивно (возможно, O (n ^ 2) окружности и, возможно, n точек внутри каждого круга), это, вероятно, намного быстрее, чем в типичных случаях проблемы. Если вы хотите быть умнее об этом, обратите внимание на проблему ближайшего соседа с фиксированным радиусом (лучшая бумага, которую я могу найти, здесь, но, к сожалению, там нет общедоступной версии).

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

Три сценария для умственного тестирования любого алгоритма кандидата против:

  • Найдите местоположение, которое максимизирует количество точек одновременно на расстоянии единицы и линии видимости, когда на карте имеется один "пиксель" стены.

  • Найдите местоположение, которое максимизирует количество точек одновременно на расстоянии единицы и прямой видимости, когда на карте есть одна прямая стена.

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

Ответ 2

Я думаю, что предложенный вами алгоритм - хороший подход:

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

  • Накопите все эти круги вместе со своей общей стоимостью врага/союзника, пиксель с наибольшей оценкой - это ваше решение.

Одна оптимизация, которую вы могли бы сделать, такова:

  • хранить цели противника в быстрой пространственной структуре данных, такой как дерево KD
  • Для каждого врага найдите количество соседних врагов на расстоянии 2 * r
  • сортировать врагов по количеству своих соседей, спускаться
  • пройдите список врагов, начиная с врага с большинством соседей, и создайте карту аккумулятора в радиусе 2 * r вокруг текущего врага.
  • Если у текущего врага есть n соседей, это означает, что вы можете достичь не более (n + 1) * e с этим противником, предполагая, что вы нажмете на всех своих соседей и не на союзников. Поэтому, если у оставшихся врагов не будет достаточного количества соседей, чтобы превысить текущий лучший результат, вы можете остановить поиск.

(это предполагает, что удары по союзникам не улучшают ваш результат)

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

Ответ 3

Вот способ сделать это более эффективно: -

  • Сканирование сетки для солдат
  • Ведение оценок для каждой точки сетки, первоначально на нуле
  • Для каждого солдата вычислить все точки по окружности круга, центрированного воином и радиусом r
  • Пройдите через луч, соединяющий солдата и указывающий на окружность.
  • Проверьте, есть ли в настоящий момент препятствие, связанное с лучом солдата, и точка по окружности.
  • Если препятствие не найдено, добавьте e или в соответствии с солдатом на счет текущей точки и перейдите к следующему.
  • else перерыв и продолжение следующей точки по окружности
  • Поддерживайте наивысший результат, достигнутый при обновлении оценки, а также точки.
  • Точкой и счетом в конце будет оптимальная зона падения

Сложность времени: -

Сетка сканирования: - O(N*M)

Найдите окружность: - O(r)

Траверс луч: - O(r)

Работа над солдатом: - O(r)*O(r) = O(r^2)

Общая временная сложность = O(N*M + S*r^2) где S - количество солдат