我正在尝试制作一款游戏,玩家必须在游戏板上从头到尾找到自己的方式。 ![游戏板] [1]
如你所见,这个游戏板包含一堆红色的圆形障碍物。为了赢得比赛,玩家必须移除最少量的障碍物。所以我的问题是,我如何以编程方式找出要移除的最小障碍物,以释放路径?自由路径将被视为空间之间的空间,圆圈不重叠且不接触。
所以我真正需要的是要移除的最小圆圈量,我不需要实际的路径。有一个简单的方法吗?
为了补充对这个游戏板的理解,每个圆圈都有相同的半径,并受到黑线的限制。
也没有必要直线移动。
答案 0 :(得分:17)
这是一个图论maximum flow
问题。
假设每个圆都是图中的一个节点。另外引入了2个特殊节点:TOP
和BOTTOM
。如果这些节点与TOP / BOTTOM侧相交,则将这些节点连接起来。如果圆相交,则将与圆相对应的节点相互连接。
现在您需要在此图表中找到最小切割,将 TOP作为源并将 BOTTOM作为接收器,反之亦然。您可以使用Max-flow_min-cut_theorem来解决它。它说的是Minimum-cut problem
与Max-flow问题等价。您可以在TopCoder上找到有关如何解决Max-Flow problem
的详细信息。
由于我们只能通过每个节点一次,我们应该将节点转换为容量的有向边,其中每个节点具有节点内和节点外。 max-flow算法将解决结果图上的问题,并考虑到我们正在删除圆而不是圆之间的连接这一事实。对于此问题,删除图形中的节点而不是边缘总是更好的决定,因为我们总是可以通过删除节点来删除任何边缘。另外删除节点可能导致删除多个边缘。
顺便说一下,在Uva Online Judge上可以找到类似的问题。尝试在法官身上解决这个问题是个好主意,然后你就会确定你的解决方案是正确的。
答案 1 :(得分:13)
在试图想象列昂尼德所写的内容时,我制作了以下图表。
答案 2 :(得分:6)
对于图表翻译,这样的事情可能有用。
如果两个圆重叠,则在两个圆之间做一个墙(蓝线)。不要忘记添加顶部和底部边框。这会产生几个区域。这些将是图表的所有节点。
接下来,我们必须找到边缘,从一个节点到另一个节点的成本。拿两个相邻区域(共用一堵墙)。然后通过蛮力,或者你想出的聪明方法,确定从这个区域移动到另一个区域的成本。如果删除圆圈,则表示删除了转到该圆圈的所有墙壁。如果这使得两个区域成为一个区域,则边缘的成本为1.如果需要移除两个圆(它们具有的所有墙)以组合这两个区域,则成本为2.等等。
绘制了一些边缘(绿色)。我们必须从起始区域到结束区域。你现在有一个日常加权图。
我认为这可以改进很多,但我把它留作练习=)
在这种情况下,最小值为3。
警告,图片是手工绘制的,我确实忘记了几个墙壁,边缘和区域。仅用于说明目的。
答案 3 :(得分:3)
好吧,所以我决定在pygame中对此进行可视化。
事实证明,这比我预期的要强很多。
其他建议中的想法是使用最大流量。从源到汇的流动瓶颈是流量最密集的地方。如果我们在这个密集的瓶颈上将图形切成两半(即 min-cut ),那么我们有最小的圆圈数。 它发生在maxflow = min-cut。
以下是我采取的步骤:
创建pygame世界,我可以随机生成圆圈
设定功能以计算圆圈之间的所有碰撞:
这涉及按x坐标对圆圈进行排序。现在找到Circle [0]的所有碰撞,我继续沿着阵列测试进行碰撞,直到找到一个圆圈,其中sx值大于2 *半径大于circle [0]' sx值,然后我可以弹出圆圈[0]并重复这个过程..
步骤4-5在" findflow()功能"
中执行创建表示带节点的圆的基本无向网络X图。仅在相应的圆圈碰撞时才连接节点。
这就是它开始变得艰难的地方..我从我的无向图中创建一个新的有向图。因为我需要通过圆(即节点)而不是边缘来计算流量,我需要将每个节点分成两个节点,边缘介于两者之间。
假设我将节点X连接到节点Y(Y< - > X)(在原始图中)。
我将X更改为Xa和Xb,以便Xa连接到Xb(Xa> Xb) Y也变为(Ya-> Yb)。
我还需要添加(Yb-> Xa)和(Xb-> Ya)来表示X和Y之间的原始连接。
无向图中的所有边都被赋予容量= 1 (例如,您只能跨过一次圆圈)
flowValue是一个整数。它是最小切割(或从源到下沉的最大流量)。 这是我们一直在寻找的答案。它代表我们需要删除的最小圈数。
奖金分配:
我想...为什么要停在这里,要删除的圈数很好,但我想知道我需要删除的确切圆圈。要做到这一点,我需要找出在flowGraph上实际发生最小切割的位置。我设法弄清楚如何做到这一点,但是我的实现在某个地方有一个错误,所以它有时会选择稍微错误的错误,所以得到错误的圈子。
要找到最小切割,我们将使用步骤6中生成的flowGraph。这个想法是这个图的瓶颈将是最小的。如果我们尝试从源头到水槽的流动,我们将卡在瓶颈上,因为瓶颈周围的所有边缘都将达到最大容量。因此,我们只需使用DFS(深度优先搜索)尽可能向下流动。 DFS只允许在流程图中沿最大容量的边缘移动。 (例如,他们的流量为0而不是1)。使用源代码中的DFS,我记下了我可以看到的所有节点,将它们存储在self.seen中。现在在DFS之后,对于所见的所有节点,我检查节点是否具有到达DFS中未见的节点的最大容量边缘。所有这些节点都在最小切割上。
以下是我跑过的一个模拟图片:
并且删除了圆圈后,我用油漆填充(你可能需要放大一点以确定圆圈之间确实存在间隙):
学习:
即使在python中速度也可以,运行1000圈即可。
这比我想象的要难,而且我仍然有一个错误,试图使用DFS来找到原始的圆圈。 (如果有人可以帮助找到那个很棒的错误。)
一开始代码很优雅,虽然我不断添加黑客来改变可视化等。
工作代码(除了DFS中的轻微偶然错误):
__author__ = 'Robert'
import pygame
import networkx
class CirclesThing():
def __init__(self,width,height,number_of_circles):
self.removecircles = False #display removable circles as green.
self.width = width
self.height = height
self.number_of_circles = number_of_circles
self.radius = 40
from random import randint
self.circles = sorted(set((randint(self.radius,width-self.radius),randint(2*self.radius,height-2*self.radius)) for i in range(self.number_of_circles)))
self.sink = (self.width/2, self.height-10)
self.source = (self.width/2, 10)
self.flowValue,self.flowGraph = self.find_flow()
self.seen = set()
self.seen.add(self.source)
self.dfs(self.flowGraph,self.source)
self.removable_circles = set()
for node1 in self.flowGraph:
if node1 not in self.seen or node1==self.source:
continue
for node2 in self.flowGraph[node1]:
if self.flowGraph[node1][node2]==1:
if node2 not in self.seen:
self.removable_circles.add(node1[0])
def find_flow(self):
"finds the max flow from source to sink and returns the amount, along with the flow graph"
G = networkx.Graph()
for node1,node2 in self.get_connections_to_source_sink()+self.intersect_circles():
G.add_edge(node1,node2,capacity=1)
G2 = networkx.DiGraph()
for node in G:
if node not in (self.source,self.sink):
G2.add_edge((node,'a'),(node,'b'),capacity=1) #each node is split into two new nodes. We add the edge between the two new nodes flowing from a to b.
for edge in G.edges_iter():
if self.source in edge or self.sink in edge:
continue #add these edges later
node1,node2 = edge
G2.add_edge((node1,'b'),(node2,'a'),capacity=1) #if we flow through a circle (from node1a to node1b) we need to be able to flow from node1b to all node1's children
G2.add_edge((node2,'b'),(node1,'a'),capactiy=1) #similarly for node2..
for node in G[self.source]:
G2.add_edge(self.source,(node,'a'))
for node in G[self.sink]:
G2.add_edge((node,'b'),self.sink)
flowValue, flowGraph = networkx.ford_fulkerson(G2,self.source,self.sink)
return flowValue, flowGraph
def dfs(self,g,v):
"depth first search from source of flowGraph. Don't explore any nodes that are at maximum capacity. (this means we can't explore past the min cut!)"
for node in g[v]:
if node not in self.seen:
self.seen.add(node)
if g[v][node]!=1 or v==self.source:
self.dfs(g,node)
def display(self):
self.draw_circles()
self.draw_circles(circle_radius=5, circle_colour=(255,0,0))
if not self.removecircles:
lines = self.intersect_circles()
self.draw_lines(lines)
self.draw_source_sink()
def draw_circles(self,circle_radius=None,circle_colour=(0,0,255),circles=None):
if circle_radius is None:
circle_radius = self.radius
if circles is None:
circles = self.circles
circle_thickness = 2
for pos in circles:
cc = circle_colour if pos not in self.removable_circles else (100,200,0) #change colour of removable circles
ct = circle_thickness if pos not in self.removable_circles else 4 #thicken removable circles
if pos not in self.removable_circles or not self.removecircles:
pygame.draw.circle(screen, cc, pos, circle_radius, ct)
def intersect_circles(self):
colliding_circles = []
for i in range(len(self.circles)-1):
for j in range(i+1,len(self.circles)):
x1,y1 = self.circles[i]
x2,y2 = self.circles[j]
if x2-x1>2*self.radius+5: #add 5 to make a more obvious gap visually
break #can't collide anymore.
if (x2-x1)**2 + (y2-y1)**2 <= (2*self.radius)**2+5:
colliding_circles.append(((x1,y1),(x2,y2)))
return colliding_circles
def draw_lines(self,lines,line_colour=(255, 0, 0)):
for point_pair in lines:
point1,point2 = point_pair
try:
tot = self.flowGraph[(point1,'b')][(point2,'a')] + self.flowGraph[(point2,'b')][(point1,'a')] #hack, does anything flow between the two circles?
except KeyError:
tot = 0
thickness = 1 if tot==0 else 3
lc = line_colour if tot==0 else (0,90,90)
pygame.draw.line(screen, lc, point1, point2, thickness)
def draw_source_sink(self):
self.draw_circles(circles=(self.sink,self.source),circle_radius=15,circle_colour=(0,255,0))
bottom_line = ((0,self.height-3*self.radius),(self.width,self.height-3*self.radius))
top_line = ((0,3*self.radius),(self.width,3*self.radius))
self.draw_lines([top_line, bottom_line],line_colour=(60,60,60))
if not self.removecircles:
self.draw_lines(self.get_connections_to_source_sink(),line_colour=(0,255,0))
def get_connections_to_source_sink(self):
connections = []
for x,y in self.circles:
if y<4*self.radius:
connections.append((self.source,(x,y)))
elif y>height-4*self.radius:
connections.append((self.sink,(x,y)))
return connections
def get_caption(self):
return "flow %s, circles removes %s" %(self.flowValue,len(self.removable_circles))
time_per_simulation = 5 #5 seconds
width, height = 1400, 600
background_colour = (255,255,255)
screen = pygame.display.set_mode((width, height))
screen.fill(background_colour)
from pygame.locals import USEREVENT
pygame.time.set_timer(USEREVENT+1,time_per_simulation*1000)
simulations = 0
simulations_max = 20
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == USEREVENT+1:
if simulations % 2 ==0:
world = CirclesThing(width,height,120) #new world
else:
world.removecircles = True #current world without green circles
screen.fill(background_colour)
world.display()
pygame.display.set_caption(world.get_caption())
pygame.display.flip()
if simulations>=2*simulations_max:
running = False
simulations+=1
if False:
pygame.image.save(screen,'sim%s.bmp'%simulations)
答案 4 :(得分:1)
一种选择是先删除重叠或触摸次数最多的圈子。在您删除每个之后,检查它是否是解决方案,如果不是继续删除。
var circle;
circle = findMostOverlapCircle();
while(circle != null) {
circle.remove();
circle = findMostOverlapCircle();
}