@[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()集合去重。
- 先确定整个字符串的第一个字母是谁,对于长度为n的字符串吗,共有n种情况;
- 然后,问题就简化成从 返回的字符串中的字母的排列组合 变成了 返回第一个字母+除去第一个字母外的字符串的排列组合 ,类似于分治法的思想,解决一个大问题很难解决,我们就去解决它的子问题,利用子问题去解决大问题,其实就是递归思想。
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
- 深度为2:(())(()) (())()() ()(())() ()()(()) (()())() ()(()()) (()()()) 7
- 深度为3:((()))() ()((())) ((()())) (()(())) ((())()) 5
- 深度为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,n]中。
对于第一点的规则就是奇数项都比前一项大,偶数项都比前一项小,这个我们可以通过用一个二维状态(dp[i][j]
)来进行标记。比如对于dp[i][j]=x
,其具体的逻辑意义是:在第i个位置上选择数字j时共有x个方案。怎么来得到dp[i][j]呢?
这显然和i的奇偶性有关:
当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];
当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];
于是我们就得到了本题的状态转移方程为:

最后还需要对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]
的意义根据位置的奇偶性进行如下重定义:
- 当i为奇数时,
dp[i][j]
表示第i个数选择大于等于数j的方案数; - 当i为偶数时,
dp[i][j]
表示第i个数选择小于等于数j的方案数。
这样的改变有点类似于将奇数位上的数用后缀数组来表达,而偶数位上的数用前缀数组来表达。那么此时我们的动态转移方程也会发生相应的改变,如下:
- 当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]
。 - 当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()