如何快速找到最佳炸弹投放区?

12

我得到了以下作业:

给定一个由四种颜色的像素组成的图像。颜色对应于地形,敌人,盟友和墙壁。可以在任何坐标(一对整数)上投掷炸弹。您还可以提供以下信息:

  • r - 炸弹的爆炸半径(以像素为单位,正整数)
  • e - 杀死敌人的分数
  • a - 杀死盟友的分数

(例如 r = 10e = 1a = -2

当炸弹落下时,在其爆炸半径内的所有敌人和盟友都会死亡,除非它们与炸弹之间有墙(即连接士兵与炸弹的非抗锯齿线穿过墙)。当炸弹落在墙上时,该特定像素的行为就像普通地形一样。其余的墙仍然是墙。

您的初始分数为0。找到您应该丢下一枚炸弹以获得最佳分数的坐标。如果有多种最优解,则返回其中任何一种。

以下是一个裁剪,调整大小并更改颜色以提高可读性的示例图像:

Example four-colored image

我得到的原始图像可以在此处找到。

我已经想出来的

我知道 brute-force 这个问题是一个糟糕的解决方案。当没有墙壁时,我想出了一种快速解决它的方法。以下是一些伪代码:

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

当然,这个'snippet'可以被优化,但在这种形式下更易理解。通过用墙来限制高度图圆,可以将其扩展为完整的解决方案。绘制圆很简单,许多图像处理库都提供了绘制圆的函数,因此也许最好的方法是先绘制圆形,然后在它们上面绘制墙壁,并从中心填充圆形,停止在墙壁或圆形边界上。在实现时,我会检查性能。

如果没有限制圆形,我会这样做:

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实现它。 顺便说一句,我相信我的老师允许我在SO上发布这个问题,我甚至认为他期望我讨论并实现最佳解决方案。

你觉得分配高度图并对它们进行乘法和加法比简单地 brute-force 更快吗?此外,“慢”一词的定义是什么?事物只有相对于更快的替代品才会变慢。你的硬件慢吗?你甚至有速度方面的限制吗?在这个任务中有这么多条件,可能没有什么比 brute-force 更精确可靠了。 - Alexander Shukaev
@Haroogan,就像我说的那样,我知道它可以被优化。高度图只是解释我的算法的好方法。我的老师说,它必须足够快,以便在普通笔记本电脑上平均一分钟内解决示例地图。即使使用暴力方法,也应该能够实现这一点,但我无法忍受粗糙、毫无头脑的解决方案。我正在寻求一个漂亮、优雅的算法来解决这个问题,或者提出改进我的解决方案的建议。 - gronostaj
2
太多不可预测的条件。墙可以有任何几何形状。你必须采用蛮力方法:测试每个像素周围半径内的敌人是否存在,并从敌人/盟友射出光线到该像素,以找出与墙壁的交点(如果有的话)并将其排除。这似乎是获得可靠结果的唯一方法。我担心没有典型的数学理论可以优雅地解决这个问题。把它看作是纯粹的蛮力追踪光线(除了优化)。 - Alexander Shukaev
这个任务的一个好处是它很容易并行化。例如,你可以简单地使用OpenMP来循环。或者你可以手动将域划分为所需的模式,并在单独的线程中运行每个子域的处理(正是OpenMP对循环所做的事情,但更少的麻烦)。你可以这样做,因为每个测试的结果不相互依赖,在这个意义上,任务非常简单。例如,任何现代GPU都可以轻松完成这个任务。只要利用硬件,你就会获胜。 - Alexander Shukaev
1
这听起来像是一个很棒的 Code Golf。 - bobobobo
3个回答

7
这里是我希望能引发一些讨论的部分答案:
解决任何问题的第一条规则是找到一个更简单的问题。在这种情况下,我们可以问:
如果没有墙壁,什么样的解决方案会比较好?
并进一步缩小范围:
如果没有墙壁或敌人,什么样的解决方案会比较好?
甚至可以进一步缩小范围:
如果没有墙壁或敌人,并且炸弹的半径为1,什么样的解决方案会比较好?
这相当于说:
给定一组点,将一个单位圆盘定位以覆盖尽可能多的点。
很酷。这感觉像一个不错的、坚实的、与领域无关的问题,许多人肯定遇到过。快速搜索一下,哇嘿,我们发现了很多相关资源,包括this SO question
在这个问题中,被接受的答案做出了一个重要的观察:如果我们有一个覆盖最多点的圆盘,我们可以移动该圆盘以得到另一个至少带有两个边缘点的圆盘。因此,如果我们对每一对距离小于2的点构造通过该点对的两个单位圆(总共为O(n^2)个圆),则其中一个圆保证包含可能的最大点数。

这可以轻松地适应您问题的“没有墙壁”版本。虽然它在朴素情况下是O(n^3)(可能是O(n^2)个圆和可能在每个圆内部的n个点),但在典型的问题实例中,它可能比那快得多。如果你想聪明些,请查看固定半径最近邻问题(我能找到的最好的论文在这里,但不幸的是没有公共版本)。 如何引入墙壁呢?如果一个圆盘与一堵墙相交,我们无法可靠地移动它以使两个点位于边缘并保持相同的分数。我会考虑一下,希望在此期间其他人也有一些想法。 对任何候选算法进行心理测试的三种情况:

  1. 找到最大化单位距离和视线内同时存在的点数的位置,当地图上只有一个像素的墙时。

  2. 找到最大化单位距离和视线内同时存在的点数的位置,当地图上只有一堵直墙时。

  3. 找到最大化单位距离和视线内同时存在的点数的位置,当墙壁在地图上形成一个单一的中空正方形时。


1
我认为你提出的算法是一个很好的方法:
  • 对于每个敌人/盟友,绘制所有位置的部分遮挡圆,这些位置可以用给定的墙壁投掷炸弹并击中目标。

  • 将所有这些带有各自敌人/盟友成本的圆累加在一起,得到最高分数的像素即为解决方案

你可以进行以下优化:

  • 使用快速的空间数据结构(如KD树)存储敌人目标
  • 对于每个敌人,找到距离2*r以内的相邻敌人数量
  • 按相邻敌人数量降序排序敌人
  • 从具有最多邻居的敌人开始,仅在当前敌人周围2*r半径范围内构建累加器映射。
  • 如果当前敌人有n个邻居,则假设你也打中了他的所有邻居而没有盟友,那么你最多只能获得(n+1)*e的得分。因此,如果剩余的敌人没有足够的邻居来超过当前的最佳得分,你可以停止搜索。

(这假设打中盟友不会提高你的得分)

在固定半径内查找所有最近的邻居是一个经过充分研究的问题,Python 中应该有几个有效的实现可用。

0

以下是以更高效的方式实现的方法:

  1. 扫描士兵所在的网格
  2. 最初将网格中每个点的分数维护为零
  3. 对于每个士兵,计算以该士兵为圆心、半径为r的圆周上的所有点
  4. 遍历连接士兵和圆周上点的射线
  5. 检查连接士兵和圆周上点的射线是否有障碍物
  6. 如果没有发现障碍物,则根据士兵添加“e”或“a”到当前点的分数,并继续移动到下一个点
  7. 否则,中断并继续处理圆周上的下一个点
  8. 维护在更新分数时实现的最高分数和相应的点
  9. 最终得分最高的点即为最佳投放区域

时间复杂度:-

扫描网格: - O(N*M)

找到圆周: - O(r)

遍历射线: - O(r)

每个士兵完成的工作: - O(r)*O(r) = O(r^2)

总时间复杂度= O(N*M + S*r^2),其中S是士兵数量


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接