栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

蓝桥杯 [第11届决赛] 补给 Python 20分

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

蓝桥杯 [第11届决赛] 补给 Python 20分

虽然只拿了20分,但是因为思路比较重要,还是决定记录一下

这个题n的数据规模是 [1, 20],所以思路就是 图 + 状态压缩

首先读取输入,得出这个图的邻接矩阵

from math import inf

num, limit = map(int, input().split())
loc = [list(map(int, input().split())) for idx in range(num)]
# 记录每个顶点的坐标
adj = [[inf for _ in range(num)] for __ in range(num)]
# 初始化邻接矩阵

for begin in range(num):
    x_b, y_b = loc[begin]
    for end in range(begin + 1, num):
        x_e, y_e = loc[end]
        value = ((x_b - x_e) ** 2 + (y_b - y_e) ** 2) ** 0.5
        # 计算距离
        if value <= limit:
            adj[begin][end] = adj[end][begin] = value
            # 如果距离允许则直接填入邻接矩阵

用二进制数来表示经过的地点。如0000表示停留在起点 (第1个地点) 未经过任何地点,0010表示从起点出发经过第2个地点,1111表示从起点出发经过第2、3、4个地点并返回起点。现在依据“经过几个地点”对二进制状态进行分层

bin函数可以将整数转化为二进制字符串,如:5 -> "0b101"。定义函数count_1得出经过地点的数目n,然后利用位运算筛除所有包含第1个地点的状态。假设总共有4个地点,因为第1个地点是要最后到达的,所以最终状态应该是1111 (15),即 (1 << 4) - 1,把这个状态分在最后一层

def count_1(num):
    return bin(num).count("1")

dp = [{} for _ in range(num + 1)]
# 索引为n的字典记录已经过起点和n个地点的状态
for choose in range(1 << num):
    if not choose & 1:
        # 筛选掉所有包含起点的状态
        dp[count_1(choose)][choose] = inf
dp[-1][(1 << num) - 1] = inf
# 在最后一层添加包含起点的状态
dp[0][0] = 0
# 初始化起始状态

在创建邻接矩阵的时候已经保证了飞机单次飞行的距离不会超限,但这样使在做状态转移时需要考虑某地点被多次经过、状态转移过于繁琐。所以用一下Floyd算法处理一下邻接矩阵,求出多源最短路

def Floyd():
    for mid in range(num):
        for begin in range(num):
            for end in range(begin + 1, num):
                state = min([adj[begin][end], adj[begin][mid] + adj[mid][end]])
                adj[begin][end] = adj[end][begin] = state
Floyd()
# 求出多源最短路

我记得我看的教程是用了三维矩阵,但是我觉得没必要,直接在邻接矩阵上面动手就可以。笔者不才,也没法说个所以然

用一维列表stay记录一下每个状态对应的停留点,就可以逐层做状态转移了

stay = [num for _ in range(1 << num)]
# 记录每个状态对应的最终停留点
stay[0] = 0
for count in range(1, num + 1):
    for last_choose in dp[count - 1]:
        # 遍历上一个状态
        last_dist = dp[count - 1][last_choose]
        # 上一个状态对应的距离
        begin = stay[last_choose]
        # 上一个状态的停留点记为begin
        if last_dist < inf:
            for cur_choose in dp[count]:
                # 遍历当前状态
                if last_choose | cur_choose == cur_choose:
                    # 状态转移合法
                    cur_dist = dp[count][cur_choose]
                    new = len(bin(cur_choose - last_choose)[2:]) - 1
                    # 当前状态停留点记为new
                    state = last_dist + adj[begin][new]
                    # 当前状态对应的距离
                    if state < cur_dist:
                        # 比较取最优
                        stay[cur_choose] = new
                        dp[count][cur_choose] = state

last_choose (二进制状态) 取自 dp[count-1],cur_choose 取自 dp[count],所以已经过地点的数目必满足:cur_choose - last_choose == 1。假设last_choose是0010,cur_choose是0101,利用 last_choose | cur_choose == cur_choose 这个条件可以筛除掉这种不合法的状态转移

假设last_choose是0010,cur_choose是0110,cur_choose - last_choose = 0100,用len(bin(cur_choose - last_choose[2:])) - 1 可以求出新经过的地点索引

最后输出最后一层唯一一个状态的值

result = dp[-1].popitem()[1]
print(f"{round(result, 2):.2f}")

思路看着没问题哇,如果有好心人给个测评数据校对就好了 —— 也请各路大佬指正

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/738275.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号