参考链接:poj2870Light Up(迭代加深搜索)_ophunter的专栏-优快云博客
一 说明:
1.1 题目大致意思是:放置灯以照亮整个矩阵,返回所需灯的最小数量。
给定一个矩阵如图(a)所示,其中黑色方框表示障碍物,障碍物的编号表示其周围需要的灯的数量(无编号的障碍物所需的灯数不限);除此之外,空白网格所在行列也需要有灯存在,以便于照亮该空白网格。
1.2 由于涉及寻找所有方案,所以确定本题用DFS解决。
1.2.1 将问题分解为两级DFS:
第一级:以障碍物所需灯数为目标,寻找所有“照亮障碍物“”的安灯方案
第二级:对第一级结果中的每一个方案进行安灯,以“照亮剩余的空白网格”。
因此,两级方案所需的最小灯数,即为答案。
1.2.2 设计原理及注意事项
## 0 程序设计时,用不同数值的元素值反映各个信息:
空白用-10表示,
照亮后的行、列空白用-20表示,
点灯位置赋值为-30。
## 1 编号为0的障碍物的4邻域为禁区,不能放置灯;
## 2 以编号最大的障碍物的邻接点为初始顶点,进行DFS搜索;
## 3 对当前障碍物的邻点放置灯时,该灯周围如果有其他障碍物,也将被照亮;
## 4 本题分为2级DFS:第一级,为障碍物点灯,寻找所有可行的方案;第二级,对各个可行方案中的空白点灯
## 4.1 在确定障碍物的点灯方案后,当对空白网格进行点灯时,灯的周围不能有障碍物,否则将使得障碍物的点灯方案有误。
## 4.1.1 如果为空白网格点灯时,其周围存在障碍物,则将该位置赋值为-100;再继续对其他空白进行点灯,直到无-10存在。最后,判断是否有-100,无-100说明点灯成功。
# # 5. 终止条件
# # 第一级dfs:所有障碍物的所需点灯均已安装完毕
# # 第二级dfs: 所有空白格均已点灯完毕
# # 6 本程序需要查询所有方案,不能使用return True or False,而是使用 return。
1.2.3 其他注意事项:
1.3 关键问题在于实施细节。
1.3.1 本人用了整整3天才做完。太菜了。代码超长。
1.3.2 我觉得应该绘制一个思维导图,用于呈现本题的程序流程,否则数天之后,本人将不记得这程序的设计逻辑。
1.4 思维导图有点可怕:
1.4.1 总图
1.4.2
查看PDF吧,实在是太庞大了。
POJ-2870LightUp+DFS(1级DFS+1级DFS)+Python-思维导图-互联网文档类资源-优快云下载
1.4.3 或许应该写成Word文档。
二 代码实现
# poj2870Light Up(迭代加深搜索)
# https://blog.youkuaiyun.com/ophunter_lcm/article/details/9318079?locationNum=10&fps=1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_title~default-0.no_search_link&spm=1001.2101.3001.4242.1
## 结果正确,但是,太耗时了。考虑,将重复的方案去掉。
##
# 试一试吧 20211221 2108
## 0 程序设计时,用不同大小的元素值反映各个信息:空白用-10表示,照亮后的行。列空白用-20表示,点灯位置赋值为-30。
## 1 编号为0的障碍物的邻域为禁区,不能放置灯
## 2 以编号最大的障碍物的邻接点为初始顶点,进行DFS搜索
## 3 对当前障碍物的邻点放置灯时,该灯周围如果有其他障碍物,也将被照亮
## 4 本题分为2级DFS:第一级,为障碍物点灯,寻找所有可行的方案;第二级,对各个可行方案中的空白点灯
## 4.1 在确定障碍物的点灯方案后,当对空白网格进行点灯时,灯的周围不能有障碍物,否则将使得障碍物的点灯方案有误。
## 4.1.1 如果为空白网格点灯时,其周围存在障碍物,则将该位置赋值为-100;再继续对其他空白进行点灯,直到无-10存在。最后,判断是否有-100,无-100说明点灯成功。
# 5. 终止条件
# # 第一级dfs:所有障碍物的所需点灯均已安装完毕
# # 第二级dfs:所有空白格均已点灯完毕
# 6 需要查询所有方案的时候,应该不能使用return True or False,而是使用 return
# # 一些约束条件:
# 1. 障碍旁边的灯的个数与其编号相同
# 2. 同一行或列最多是有一个灯,即已经被点亮的地方,不能再放置灯
# 3. 所有的空白均被照亮
# 4. 灯可以照亮未被障碍堵塞的整行或整列
# 8 迭代返回和终止的条件是什么?
import collections
import math
#1.1 数据转化
# 1.1 注意障碍数是否为0
# 1.2 注意将障碍的横纵坐标均减去1,再进行赋值
def sub2Dict(RR,CC,B_num,B_data):
##
subData = [[-10]*CC for i in range(RR)] # # # 空白用-10表示
## 1 将障碍添加到数据矩阵中,同时获得障碍矩阵
Barrier_dict = collections.defaultdict(list)
if B_num > 0:
for bi in range(B_num):
rr,cc,kk = map(int,B_data[bi].strip().split(' '))
subData[rr-1][cc-1] = kk # 横纵坐标减去1,使之从下表0开始编号
# Barrier_dict[kk] = [str(rr-1)+';'+str(cc-1),rr-1,cc-1,kk] # 坐标索引,横坐标、纵坐标、所需的灯数 # 隐患:只保存一次-1障碍,本字典记录的障碍数小于实际数
Barrier_dict[str(rr - 1) + ';' + str(cc - 1)] = [str(rr - 1) + ';' + str(cc - 1), rr - 1, cc - 1, kk]
## 将数据转化为dict
Data_dict = collections.defaultdict(list)
Stop_dict = collections.defaultdict(list) # 需要提前设置该空值
for ri in range(RR):
for ci in range(CC):
# 0 获得元素坐标
Data_dict[str(ri)+';'+str(ci)] = subData[ri][ci]
## 1 查找编号为0的元素的邻域,它的空位置领域,不能放置灯,即得到禁区的坐标
if subData[ri][ci] == 0:
Stop_dict = ForNeibs(ri, ci, RR, CC, subData)
return subData,Data_dict,Stop_dict,Barrier_dict
## 查找节点的可用邻域
def ForNeibs(ri,ci,RR,CC,subData):
Neib_Dict = collections.defaultdict(list)
## 查找当前节点的有效的4邻域,且只有其为空格时才属于有效的潜在置灯位置
## 是否加入,不属于禁区呢?
if 0 <= ri + 1 <= RR - 1 and subData[ri + 1][ci] == -10:
node1 = str(ri + 1) + ';' + str(ci)
Neib_Dict[node1] = [subData[ri + 1][ci]]
if 0 <= ri - 1 <= RR - 1 and subData[ri - 1][ci] == -10:
node1 = str(ri - 1) + ';' + str(ci)
Neib_Dict[node1] = [subData[ri - 1][ci]]
if 0 <= ci + 1 <= CC - 1 and subData[ri][ci + 1] == -10:
node1 = str(ri) + ';' + str(ci + 1)
Neib_Dict[node1] = [subDat