不忘初心,
牢记使命。

蓝桥python组模拟赛2(完结)

2021-04-01 大聪明 0评论 309 0喜欢

@[TOC]

12.5MB

【问题描述】
在计算机存储中,12.5MB是多少字节?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。



1Byte=8Bit,它们之间存在着换算关系。bit中文叫「位/比特」,是计算机中存储数据的最小单位,它的值是0或1;Byte中文叫「字节」,是计算机文件大小的基本计算单位。

1B(字节)=8bit(位)
1KB=1024B
1MB=1024KB
1GB=1024M
1TB=1024G

res = 12.5 * 1024 * 1024
print(res)

13107200

最多边数

【问题描述】
一个包含有2019个结点的有向图,最多包含多少条边?(不允许有重边)
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


设结点数为n

无向图边数:n * (n - 1) / 2

有向图边数:n * (n - 1)

res = 2019 * (2019 - 1)
print(res)

4074342

问题描述
  一个包含有2019个结点的无向连通图,最少包含多少条边?
答案提交
  这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。

答案:2018
解决办法:一个具有n个节点的无向连通图,其最少含有的边为n-1(其实就是这个图的生成树)。

单词重排

【问题描述】
将LANQIAO中的字母重新排列,可以得到不同的单词,如LANQIAO、AAILNOQ等,注意这7个字母都要被用上,单词不一定有具体的英文意义。
请问,总共能排列如多少个不同的单词。
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


首先利用递归的思想去求所有的组合结果,然后利用set()集合去重。

  1. 先确定整个字符串的第一个字母是谁,对于长度为n的字符串吗,共有n种情况;
  2. 然后,问题就简化成从 返回的字符串中的字母的排列组合 变成了 返回第一个字母+除去第一个字母外的字符串的排列组合 ,类似于分治法的思想,解决一个大问题很难解决,我们就去解决它的子问题,利用子问题去解决大问题,其实就是递归思想。
def perm(s=''):  # 默认参数为空字符串
    if len(s) <= 1:
        return s

    s1 = set()  # 集合去重

    for i in range(len(s)):
        for j in perm(s[0:i] + s[i+1:]):  # 去除一个字符剩下的串的排列组合
            s1.add(s[i] + j)  # 存储排列的字符串,集合特性自动去重

    return s1


def main():
    s = 'LANQIAO'
    s1 = perm(s)
    # print(s1)
    print(len(s1))  # 2520

if __name__ == '__main__':
    main()

上边为python模块的正规写法,考试时建议不要这么干,直接写就行,这里吐槽一下IDLE,真难用,而且考试还要用!

考试时这样写就行:

def perm(s=''):  # 默认参数为空字符串
    if len(s) <= 1:
        return s

    s1 = set()  # 集合去重

    for i in range(len(s)):
        for j in perm(s[0:i] + s[i+1:]):  # 去除一个字符剩下的串的排列组合
            s1.add(s[i] + j)  # 存储排列的字符串,集合特性自动去重

    return s1

print(len(perm('LANQIAO')))

括号序列

【问题描述】
由1对括号,可以组成一种合法括号序列:()。
由2对括号,可以组成两种合法括号序列:()()、(())。
由4对括号组成的合法括号序列一共有多少种?
【答案提交】
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。


首先就四对括号,那么手动暴力破解:

  1. 深度为1:()()()() 1
  2. 深度为2:(())(()) (())()() ()(())() ()()(()) (()())() ()(()()) (()()()) 7
  3. 深度为3:((()))() ()((())) ((()())) (()(())) ((())()) 5
  4. 深度为4:(((()))) 1

一共 1 + 7 + 5 + 1 = 14 种

其次代码当然也是可以解决的!

def f(left, right):  # 利用递归解决

    global ans

    if left == 0 or right == 0:  # 无论左括号用完还是右括号匹配完就结束函数
        ans += 1  # 方法加1
        return

    f(left - 1, right)  # 先用一个左括号

    if left < right:  # 检测左括号数目如果小于右括号数目,则使用右括号
        f(left, right - 1)


ans = 0


f(4,4)  # 一共4对括号

print(ans)

反倍数

【问题描述】
给定三个整数 a, b, c,如果一个整数既不是 a 的整数倍也不是 b 的整数倍还不是 c 的整数倍,则这个数称为反倍数。
请问在 1 至 n 中有多少个反倍数。
【输入格式】
输入的第一行包含一个整数 n。
第二行包含三个整数 a, b, c,相邻两个数之间用一个空格分隔。
【输出格式】
输出一行包含一个整数,表示答案。
【样例输入】
30
2 3 6
【样例输出】
10
【样例说明】
以下这些数满足要求:1, 5, 7, 11, 13, 17, 19, 23, 25, 29。
【评测用例规模与约定】
对于 40% 的评测用例,1 <= n <= 10000。
对于 80% 的评测用例,1 <= n <= 100000。
对于所有评测用例,1 <= n <= 1000000,1 <= a <= n,1 <= b <= n,1 <= c <= n。


很简单,这里只说一下处理输入数据时的问题。

首先input()是输入数据,类型为字符串,我们先strip()一下去掉左右两边的空白,结果仍为一个字符串;然后再用split()分隔(默认是一空白分割),返回的是一个列表;之后再用map()对列表数据进行int格式化成整形,返回的是一个生成器格式数据;最后用list()一次性全部接收生成器中的所有元素,类型为列表。

n = int(input())
lst = list(map(int, input().strip().split()))

count = 0
# arr = []

for i in range(1, n+1):
    if i % lst[0] !=0 and i % lst[1] != 0 and i % lst[2] != 0:
        count += 1
        arr.append(i)

print(count)
# print(arr)

凯撒加密

【问题描述】
给定一个单词,请使用凯撒密码将这个单词加密。
凯撒密码是一种替换加密的技术,单词中的所有字母都在字母表上向后偏移3位后被替换成密文。即a变为d,b变为e,...,w变为z,x变为a,y变为b,z变为c。
例如,lanqiao会变成odqtldr。
【输入格式】
输入一行,包含一个单词,单词中只包含小写英文字母。
【输出格式】
输出一行,表示加密后的密文。
【样例输入】
lanqiao
【样例输出】
odqtldr
【评测用例规模与约定】
对于所有评测用例,单词中的字母个数不超过100。


s = input()
res = ''

for i in s:
    """加上 x y z 这三种特殊情况"""
    res += chr(((ord(i) - ord('a') + 3) % 26) + ord('a'))

print(res)

螺旋

【问题描述】
对于一个 n 行 m 列的表格,我们可以使用螺旋的方式给表格依次填上正整数,我们称填好的表格为一个螺旋矩阵。
例如,一个 4 行 5 列的螺旋矩阵如下:
1 2 3 4 5
14 15 16 17 6
13 20 19 18 7
12 11 10 9 8
【输入格式】
输入的第一行包含两个整数 n, m,分别表示螺旋矩阵的行数和列数。
第二行包含两个整数 r, c,表示要求的行号和列号。
【输出格式】
输出一个整数,表示螺旋矩阵中第 r 行第 c 列的元素的值。
【样例输入】
4 5
2 2
【样例输出】
15
【评测用例规模与约定】
对于 30% 的评测用例,2 <= n, m <= 20。
对于 70% 的评测用例,2 <= n, m <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= r <= n,1 <= c <= m。


def make_mat(x, y):
    global count
    while count <= m * n:

        while y < m and mat[x][y] == 0:  # y<m这个条件一定要放在mat[x][y]===0前边提前判断,否则,先判断是否值为0时会导致循环结束时的y值为m而报数组越界的错误
            mat[x][y] = count
            y += 1
            count += 1

        y -= 1  # 上个循环结束后,y的值为m,需要减一,否则越界
        x += 1  # 上一行读完后,行值加1

        while x < n and mat[x][y] == 0:  # 下边同理
            mat[x][y] = count
            x += 1
            count += 1

        x -= 1  # 上个循环结束后x值为n,要减一
        y -= 1  # 最后一列读完,往左推一列

        while y >= 0 and mat[x][y] == 0:
            mat[x][y] = count
            y -= 1
            count += 1

        y += 1  # 同理
        x -= 1

        while x >= 0 and mat[x][y] == 0:
            mat[x][y] = count
            x -= 1
            count += 1

        x += 1  # 同理
        y += 1


n, m = list(map(int, input().split()))
r, c = list(map(int, input().split()))

count = 1

mat = [[0] * m for i in range(n)]  # 注意二维数组的定义方法,不然会导致编一个位置其他行的相同位置也会随之改变

make_mat(0, 0)

# for line in mat:
    # print(*line)

print(mat[r - 1][c - 1])

摆动序列

【问题描述】
如果一个序列的奇数项都比前一项大,偶数项都比前一项小,则称为一个摆动序列。即 a[2i]<a[2i-1], a[2i+1]>a[2i]。
小明想知道,长度为 m,每个数都是 1 到 n 之间的正整数的摆动序列一共有多少个。
【输入格式】
输入一行包含两个整数 m,n。
【输出格式】
输出一个整数,表示答案。答案可能很大,请输出答案除以10000的余数。
【样例输入】
3 4
【样例输出】
14
【样例说明】
以下是符合要求的摆动序列:
2 1 2
2 1 3
2 1 4
3 1 2
3 1 3
3 1 4
3 2 3
3 2 4
4 1 2
4 1 3
4 1 4
4 2 3
4 2 4
4 3 4
【评测用例规模与约定】
对于 20% 的评测用例,1 <= n, m <= 5;
对于 50% 的评测用例,1 <= n, m <= 10;
对于 80% 的评测用例,1 <= n, m <= 100;
对于所有评测用例,1 <= n, m <= 1000。


具体详解可参考https://blog.csdn.net/the_ZED/article/details/106029631这篇的博主是用c写的,我用python写的代码在下边。

这个题用普通的搜索肯定很难解决,而且造成递归树过大。这时候我们通常可以用记忆化搜索或动态规划来解决。首先,动态规划解决的话我们首先要做的就是寻找状态转移方程。有两个关键的地方:

1. 位置的奇偶会影响当前位置上的取值规则;
  1. 所取值必须停在区间[1,n]中。

对于第一点的规则就是奇数项都比前一项大,偶数项都比前一项小,这个我们可以通过用一个二维状态(dp[i][j])来进行标记。比如对于dp[i][j]=x,其具体的逻辑意义是:在第i个位置上选择数字j时共有x个方案。怎么来得到dp[i][j]呢?这显然和i的奇偶性有关:

  1. 当i是奇数时,由于其比前面的每一项都大,因此对于dp[i][j]而言,其前面可以存在的合法项就有:dp[i-1][1]、dp[i-1][2]、dp[i-1][3]、……、dp[i-1][j-1]。换言之dp[i][j]=dp[i-1][1]+dp[i-1][2]+dp[i-1][3]+……+dp[i-1][j-1];
  2. 当i是偶数时,由于其比前面的每一项都小,因此对于dp[i][j]而言,其前面可以存在的合法项就有:dp[i-1][j+1]、dp[i-1][j+2]、dp[i-1][j+3]、……、dp[i-1][n]。换言之dp[i][j]=dp[i-1][j+1]+dp[i-1][j+2]+dp[i-1][j+3]+……+dp[i-1][n];

于是我们就得到了本题的状态转移方程为:

![img](https://img-blog.csdnimg.cn/20200509232934834.png)

最后还需要对dp数组进行初始化。试想,对于序列的首项而言,其并不存在所谓的“前一项”,因此在首项处可以任意取值,当然,其每个取值都只有一种方案。即:dp[1][1]=1,dp[1][2]=1,dp[1][3]=1,……,dp[1][n]=1
求出dp数组后怎么使用呢?要知道dp[i][j]的逻辑意义是在第i个位置上选择数字j时的方案数,那么在长度为m,取值范围为[1,n]的前提下,总的摆动序列的数量就可以用dp[m][1]+dp[m][2]+dp[m][3]+……+dp[m][n]来表示。

下面直接给出基于上述思路写出的完整代码(看不太懂的减一把程序copy进pycharm然后进行debug观察变量的变化情况,非常直观,不会的可以留言,我找时间会做个详细教程):

def dp(m, n):
    ans = 0
    temp = 0
    for i in range(1, n+1):
        lst[1][i] = 1  # 初始化数组
    for i in range(2, m+1):
        for j in range(1, n+1):
            temp = 0
            if i % 2 != 0:  # 奇数
                for k in range(1, j):
                    temp = (temp + lst[i-1][k]) % MOD
            else:  # 偶数
                for k in range(j+1, n+1):
                    temp = (temp + lst[i-1][k]) % MOD
            lst[i][j] = temp
    for i in range(1, n+1):
        ans = (ans + lst[m][i]) % MOD

    return ans


m, n = list(map(int, input().split()))
MOD = 10000
lst = [[0] * (n+2) for i in range(m+2)]

print(dp(m, n))

这个代码很容易理解,但是问题也很突出,那就是在DP时,我们用到了3层循环!!!(更严格的说,其时间复杂度为O(n3))这在m=n=1000的极限情况下超时无疑,因此我们不得不进行优化。

在上面之所以用了3层循环,是因为dp[i][j]这个状态表示的是其在第i个位置上选择数字j时的方案数,这是一个“1对1”的存储模式。它导致程序在后面进行状态转移时,需要对每一个dp[x][y]进行更新维护,而更新维护的时候又是从前一次的{dp[i-1][1],dp[i-1][2],……,dp[i-1][n] }中寻找。遍历dp[x][y]是一个二重循环,更新维护又是一重循环,因此上面的程序是一个三重循环。

如果我们试图降低本程序的时间复杂度,就不得不从上面的短板中寻找突破。
一个比较直观,且容易想到的改进是:能不能对dp[i][j]这个存储模式进行更改?如果我们直接将dp[i][j]的存储模式改为“1对多”,那就相当于降低了一重循环。
现在我们就来着手实现对dp[i][j]的模式更改。由于位置i的奇偶性会影响当前位置上的取值规则,因此我们也可以将dp[i][j]的意义根据位置的奇偶性进行如下重定义:

  1. 当i为奇数时,dp[i][j]表示第i个数选择大于等于数j的方案数;
  2. 当i为偶数时,dp[i][j]表示第i个数选择小于等于数j的方案数。

这样的改变有点类似于将奇数位上的数用后缀数组来表达,而偶数位上的数用前缀数组来表达。那么此时我们的动态转移方程也会发生相应的改变,如下:

  1. 当i为奇数时,由于奇数项都比前一项大,那么其前一项只能是dp[i-1][j-1]。又由于此时dp[i][j]表示的是“第i个数选择大于等于数j的方案数”,那么我们还需要额外加上当前项的后一项,即dp[i][j+1]。因此,此时dp[i][j]=dp[i-1][j-1]+dp[i][j+1]
  2. 当i为偶数时,由于偶数项都比前一项小,那么其前一项只能是dp[i-1][j+1]。又由于此时dp[i][j]表示的是“第i个数选择小于等于数j的方案数”,那么我们还需要额外加上当前项的前一项,即dp[i][j-1]。因此,此时dp[i][j]=dp[i-1][j+1]+dp[i][j-1]。

这样一来,我们就省去了上面那个算法的最内层循环,从而将时间复杂度降低到O(n2)。即使在极限情况下,改进后的算法也能从容应对。

接下来是初始化问题,我们在将dp[i][j]存储的信息进行更改后,其意义是一个累加值(随当前位置i的奇偶性而决定累加方向)。由于首项dp[1][y]所在位置1是一个奇数,那么其累加方向是自右向左,即:dp[1][1]=n,dp[1][2]=n-1,dp[1][3]=n-2,……,dp[1][n-1]=2,dp[1][n]=1。
现在求出dp数组后又怎么使用呢?还是从dp[i][j]的逻辑意义出发:“当i为奇数时,dp[i][j]表示第i个数选择大于等于数j的方案数;当i为偶数时,dp[i][j]表示第i个数选择小于等于数j的方案数”。显然,这时候我们的结果是依赖于序列长度的奇偶性:
如果序列长度为奇数,那为了能将所有的结果都包含进来,就应该用dp[m][1]来表示该波动序列的总量;
如果序列长度为偶数,那为了能将所有的结果都包含进来,就应该用dp[m][n]来表示该波动序列的总量;

下面给出改进后,本题的完整代码:

def dp(m, n):
    for i in range(1, n+1):
        lst[1][i] = n - i + 1  # 初始化数组
    for i in range(2, m+1):
        if i % 2 != 0:  # 奇数
            for j in range(n, 0, -1):
                lst[i][j] = (lst[i-1][j-1] + lst[i][j+1]) % MOD
        else:  # 偶数
            for j in range(1, n+1):
                lst[i][j] = (lst[i-1][j+1] + lst[i][j-1]) % MOD

    return lst[m][1] if m % 2 != 0 else lst[m][n]


m, n = list(map(int, input().split()))
MOD = 10000
lst = [[0] * (n+2) for i in range(m+2)]

print(dp(m, n))

通电

【问题描述】
2015年,全中国实现了户户通电。作为一名电力建设者,小明正在帮助一带一路上的国家通电。
这一次,小明要帮助 n 个村庄通电,其中 1 号村庄正好可以建立一个发电站,所发的电足够所有村庄使用。
现在,这 n 个村庄之间都没有电线相连,小明主要要做的是架设电线连接这些村庄,使得所有村庄都直接或间接的与发电站相通。
小明测量了所有村庄的位置(坐标)和高度,如果要连接两个村庄,小明需要花费两个村庄之间的坐标距离加上高度差的平方,形式化描述为坐标为 (x_1, y_1) 高度为 h_1 的村庄与坐标为 (x_2, y_2) 高度为 h_2 的村庄之间连接的费用为
sqrt((x_1-x_2)*(x_1-x_2)+(y_1-y_2)*(y_1-y_2))+(h_1-h_2)*(h_1-h_2)。
在上式中 sqrt 表示取括号内的平方根。请注意括号的位置,高度的计算方式与横纵坐标的计算方式不同。
由于经费有限,请帮助小明计算他至少要花费多少费用才能使这 n 个村庄都通电。
【输入格式】
输入的第一行包含一个整数 n ,表示村庄的数量。
接下来 n 行,每个三个整数 x, y, h,分别表示一个村庄的横、纵坐标和高度,其中第一个村庄可以建立发电站。
【输出格式】
输出一行,包含一个实数,四舍五入保留 2 位小数,表示答案。
【样例输入】
4
1 1 3
9 9 7
8 8 6
4 5 4
【样例输出】
17.41
【评测用例规模与约定】
对于 30% 的评测用例,1 <= n <= 10;
对于 60% 的评测用例,1 <= n <= 100;
对于所有评测用例,1 <= n <= 1000,0 <= x, y, h <= 10000。


可以先学习下python版最小生成树Prim和Kruskal算法

方法一prim的两种解法:

"""
解法1
Prim类接受两个参数n,m,分别表示录入的点和边的个数(点的编号从1开始递增)
接下来是m行,每行3个数x,y,length,表示点x和点y之间存在一条长度为length的边 
程序最终会打印出该无向图中的最小生成树的各个边和最小权值 
"""
import math

class Node:
    """
    Node类存放节点信息,
    node是与某个节点(实际上是用列表索引值代表的)相连接的点
    length表示这两个点之间的权值
    """
    def __init__(self, x, y, h):
        self.x = x
        self.y = y
        self.h = h


class Edge:
    """
    Edge类表示边的信息
    x,y表示这条边的两个顶点
    length为<x,y>之间的距离
    """
    def __init__(self, x, y, cost):
        self.x = x
        self.y = y
        self.cost = cost


class Prim:
    """Prim算法:求解无向连通图中的最小生成树 """
    def __init__(self, n, adj_map):
        self.n = n  # 输入的点个数
        self.adj_map = adj_map
        self.cost = 0
        self.node = [0 for i in range(n + 1)]  # 存放所有节点之间的可达关系与距离
        """图的邻接表的表示法,v是一个二维列表,其索引值代表当前节点,索引值对应的一维列表中存放的
        是与以当前索引值为顶点的相连的节点信息"""
        self.e = []  # 存放与当前已选节点相连的边
        self.vis = [False for i in range(n+1)]  # 标记每个点是否被访问过,False未访问

    def create(self):
        for i in range(1, self.n):
            for j in range(i+1, self.n+1):
                self.adj_map[i][j] = self.adj_map[j][i] = math.sqrt((self.node[i].x - self.node[j].x) ** 2 + (self.node[i].y - self.node[j].y) ** 2) + (self.node[i].h - self.node[j].h) ** 2

    def graphy(self):
        """构建图,这里用的是邻接表"""
        for i in range(1, self.n+1):
            x, y, h = list(map(int, input().split()))
            self.node[i] = Node(x, y, h)
        self.create()

    def insert(self, point):
        self.vis[point] = True
        for i in range(1, self.n+1):
            if not self.vis[i]:
                self.e.append(Edge(point, i, self.adj_map[point][i]))
        self.e = sorted(self.e, key=lambda e: e.cost)  # 把e中的所有边按边的长度从小到大排序
        # for i in self.e:
        #     print(i.cost, i.x, i.y)

    def run(self, start):
        """执行函数:求解录入的无向连通图的最小生成树"""
        self.insert(start)  # start为选择的开始顶点
        selected = 1
        # exit()
        while selected < self.n:
            for i in range(len(self.e)):
                if not self.vis[self.e[i].y]:
                    self.cost += self.e[i].cost
                    self.insert(self.e[i].y)
                    selected += 1
                    break
        return self.cost


def main():
    n = int(input())
    adj_map = [[0] * (n+1) for i in range(n+1)]
    prim = Prim(n, adj_map)
    prim.graphy()
    # print(adj_map)
    cost = prim.run(1)
    print(round(cost, 2))


if __name__ == '__main__':
    main()
import sys
import math
MAX = sys.maxsize


class Prim:
    '''
        prim算法求最小生成树
        :param graph: 图
        :return: 最小的权值
        :n:点的个数
        '''
    def __init__(self, n, graphy):
        self.n = n
        self.cost = 0
        self.v = 0
        self.graphy = graphy
        self.lowcost = []  #记录当前顶点集合到剩下的点的最低权值,“-1”表示已访问过的点,无需访问
        self.mst = []  ##记录当前被更新的最低权值来自于哪个点,相当于记录是边的起始点,lowcost的下标表示的是最低权值的终止点

    def run(self):
        for i in range(self.n):
            self.lowcost.append(self.graphy[0][i])
            self.mst.append(0)
        self.lowcost[0] = -1
        for i in range(self.n-1):
            min = MAX
            for j in range(self.n):
                if min > self.lowcost[j] and self.lowcost[j] != -1:
                    min = self.lowcost[j]
                    self.v = j
            self.cost += min
            # print(f'{self.mst[self.v]}->{self.v}:{min}')
            self.lowcost[self.v] = -1
            for k in range(self.n):
                if self.lowcost[k] > self.graphy[self.v][k]:
                    self.lowcost[k] = self.graphy[self.v][k]
                    self.mst[k] = self.v
        return self.cost


def main():
    n = int(input())
    data = [list(map(int, input().split())) for i in range(n)]
    graphy = [[MAX] * n for i in range(n)]
    for i in range(n-1):
        for j in range(i + 1, n):
            graphy[j][i] = graphy[i][j] = math.sqrt((data[i][0] - data[j][0]) ** 2 + (data[i][1] - data[j][1]) ** 2) + (data[i][2] - data[j][2]) ** 2
    # print(data)
    # print(graphy)
    prim = Prim(n, graphy)
    # cost = prim.run()
    cost = round(prim.run(),2)
    print(cost)


if __name__ == '__main__':
    main()

方法二Krukal方法:

import math
class Edge:
    """
    Edge类表示边的信息
    x,y表示这条边的两个顶点
    length为<x,y>之间的距离
    """
    def __init__(self, x, y, cost):
        self.x = x
        self.y = y
        self.cost = cost

class UnionFindSet:
    """并查集类,用于连通两个节点以及判断图中的所有节点是否连通 """
    def __init__(self, start, n):
        self.start = start  # start和n分别用于指示并查集里节点的起点和终点
        self.n = n
        self.pre = [0 for i in range(self.n - self.start + 2)]  # pre数组用于存放某个节点的上级
        self.rank = [0 for i in range(self.n - self.start + 2)]  # rank数组用于降低关系树的高度

    def init(self):
        """初始化并查集"""
        for i in range(self.start, self.n+1):
            self.pre[i] = i
            self.rank[i] = 1

    def find_pre(self, x):
        """寻找节点x的上一级节点"""
        if self.pre[x] == x:
            return x
        else:
            self.pre[x] = self.find_pre(self.pre[x])
        return self.pre[x]

    def is_same(self, x, y):
        """判断x节点和y节点是否连通"""
        return self.find_pre(x) == self.find_pre(y)

    def unite(self, x, y):
        """判断两个节点是否连通,如果未连通则将其连通并返回真,否则返回假"""
        x = self.find_pre(x)
        y = self.find_pre(y)
        if x == y:
            return False
        if self.rank[x] > self.rank[y]:
            """类似于平衡二叉树的概念"""
            self.pre[y] = x
        else:
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1
            self.pre[x] = y
        return True

    def is_one(self):
        """判断整个无向图中的所有节点是否连通 """
        temp = self.find_pre(self.start)
        for i in range(self.start+1, self.n+1):
            if self.find_pre(i) != temp:
                return False
        return True


class Kruskal:
    """Kruskal算法:求解无向连通图中的最小生成树 """
    def __init__(self, n, m):
        self.n = n  # n,m分别表示输入的点和边的个数
        self.m = m
        self.cost = 0
        self.node = [0 for i in range(n + 1)]  # 存放所有节点之间的可达关系与距离
        self.e = []  # 存放录入的无向连通图的所有边
        self.s = []  # 存放最小生成树里的所有边
        self.u = UnionFindSet(1, self.n)  # 并查集:抽象实现Graphnew,并完成节点间的连接工作以及判断整个图是否连通

    def create(self):
        # print(self.node)
        for i in range(1, self.n):
            for j in range(i+1, self.n+1):
                self.e.append(Edge(i, j, math.sqrt((self.node[i].x - self.node[j].x) ** 2 + (self.node[i].y - self.node[j].y) ** 2) + (self.node[i].cost - self.node[j].cost) ** 2))

    def graphy(self):
        """这里只是存储所有边的信息并按边的长度排序"""
        for i in range(1, self.n+1):
            x, y, h = list(map(int, input().split()))
            self.node[i] = Edge(x, y, h)
        self.create()
        self.e.sort(key=lambda e: e.cost)
        # for i in self.e:
        #     print(i.h)
        self.u.init()

    def run(self):
        """执行函数:求解录入的无向连通图的最小生成树 """
        for i in range(self.m):
            if self.u.unite(self.e[i].x, self.e[i].y):
                self.s.append(self.e[i])
            if self.u.is_one():
                """一旦Graphnew的连通分支数为1,则说明求出了最小生成树 """
                break

    def print(self):
        print(f'构成最小生成树的边为:')
        edge_sum = 0
        for i in range(len(self.s)):
            print(f'边 <{self.s[i].x},{self.s[i].y}> = {self.s[i].cost}')
            edge_sum += self.s[i].cost
        print(f'最小生成树的权值为:{edge_sum}')

def main():
    n = int(input())
    m = int((n * (n - 1)) / 2)
    kruskal = Kruskal(n, m)
    kruskal.graphy()
    kruskal.run()
    kruskal.print()

if __name__ == '__main__':
    main()

运行结果(输出自己改一下):

4
1 1 3
9 9 7
8 8 6
4 5 4
构成最小生成树的边为:
边 <2,3> = 2.414213562373095
边 <1,4> = 6.0
边 <3,4> = 9.0
最小生成树的权值为:17.414213562373096

植树

【问题描述】
小明和朋友们一起去郊外植树,他们带了一些在自己实验室精心研究出的小树苗。
小明和朋友们一共有 n 个人,他们经过精心挑选,在一块空地上每个人挑选了一个适合植树的位置,总共 n 个。他们准备把自己带的树苗都植下去。
然而,他们遇到了一个困难:有的树苗比较大,而有的位置挨太近,导致两棵树植下去后会撞在一起。
他们将树看成一个圆,圆心在他们找的位置上。如果两棵树对应的圆相交,这两棵树就不适合同时植下(相切不受影响),称为两棵树冲突。
小明和朋友们决定先合计合计,只将其中的一部分树植下去,保证没有互相冲突的树。他们同时希望这些树所能覆盖的面积和(圆面积和)最大。
【输入格式】
输入的第一行包含一个整数 n ,表示人数,即准备植树的位置数。
接下来 n 行,每行三个整数 x, y, r,表示一棵树在空地上的横、纵坐标和半径。
【输出格式】
输出一行包含一个整数,表示在不冲突下可以植树的面积和。由于每棵树的面积都是圆周率的整数倍,请输出答案除以圆周率后的值(应当是一个整数)。
【样例输入】
6
1 1 2
1 4 2
1 7 2
4 1 2
4 4 2
4 7 2
【样例输出】
12
【评测用例规模与约定】
对于 30% 的评测用例,1 <= n <= 10;
对于 60% 的评测用例,1 <= n <= 20;
对于所有评测用例,1 <= n <= 30,0 <= x, y <= 1000,1 <= r <= 1000。


考点:两圆之间的位置关系

首先建议先做一下这个题:判断两个圆之间的关系

下边是本题代码:

import math


class Circle:
    """存储每颗树的位置信息,和它的覆盖面积,并提供判断自身与另一颗树的覆盖范围的位置关系"""
    def __init__(self, x, y, r):
        self.x = x
        self.y = y
        self.r = r
        self.absolute_area = r ** 2

    def judge_relation(self, *args):
        """
        判断两圆之间的关系
        @args:多值参数
        """
        if args:  # args(这里的args就是一个元组)不为空时
            flag = [False for i in range(len(args))]
            i = 0
            for circle in args:
                dis = math.sqrt((self.x - circle.x) ** 2 + (self.y - circle.y) ** 2)
                if self.r + circle.r < dis or self.r + circle.r == dis:  # 外离和外切
                    """若两个圆属于外切和外离的情况,置flag[i]为真"""
                    flag[i] = True
                i += 1
            return flag
        else:
            # args为空,第一个圆,肯定不会存在与其他圆的位置关系情况,直接返回一个元组,因为后边的all()的参数必须为可迭代对象
            return [True, ]


class PlantTree:
    def __init__(self, n):
        self.n = n  # 待植的树的棵数
        self.tree = [0 for i in range(n)]  # 存放每一颗树的位置信息
        self.vis = []  # 存放已经种植的树
        self.total_area = 0  # 已经种植的树覆盖的总面积

    def init(self):
        for i in range(self.n):
            x, y, r = list(map(int, input().split()))
            self.tree[i] = Circle(x, y, r)
        self.tree.sort(key=lambda circle: circle.r, reverse=True)  # 半径从大到小排序,覆盖面积最大的先种

    def plant(self):
        for i in range(self.n):
            # flag = all(self.tree[i].judge_relation(*self.vis))
            if all(self.tree[i].judge_relation(*self.vis)):
                """注意all()的参数必须是可迭代对象,vis是一个列表用*打散传入"""
                # 如果当前树与已种的树不会相交,则把此树放入已种树的列表。
                self.vis.append(self.tree[i])
                self.total_area += self.tree[i].absolute_area
        return self.total_area


def main():
    n = int(input())
    plant_tree = PlantTree(n)
    plant_tree.init()
    plant_tree.plant()
    print(plant_tree.total_area)


if __name__ == '__main__':
    main()

发表评论 取消回复

电子邮件地址不会被公开。

请输入正确格式的qq邮箱
请输入以http或https开头的URL,格式如:https://libo_sober.top