欢迎交流学习~~
专栏: 蓝桥杯Python组刷题日寄
从 4 个月前开始写蓝桥杯系列,到目前为止一共是 19 篇,其中:入门篇 5 篇,简单篇 8 篇,进阶篇 6 篇。
这篇文章主要是为了为先前内容进行总结,并对其中的部分经典例题进行回顾。
蓝桥杯入门系列:
😜【蓝桥杯入门篇】Python组刷题日寄Part01
😗【蓝桥杯入门篇】Python组刷题日寄Part02
😮【蓝桥杯入门篇】Python组刷题日寄Part03
😍【蓝桥杯入门篇】Python组刷题日寄Part04
😃【蓝桥杯入门篇】Python组刷题日寄Part05
蓝桥杯简单系列:
💙【蓝桥杯简单篇】Python组刷题日寄Part01
💜【蓝桥杯简单篇】Python组刷题日寄Part02
💚【蓝桥杯简单篇】Python组刷题日寄Part03
💗【蓝桥杯简单篇】Python组刷题日寄Part04
💕【蓝桥杯简单篇】Python组刷题日寄Part05
💘【蓝桥杯简单篇】Python组刷题日寄Part06
💖【蓝桥杯简单篇】Python组刷题日寄Part07
💔【蓝桥杯简单篇】Python组刷题日寄Part08
蓝桥杯进阶系列:
🏆 Python | 蓝桥杯进阶第一卷——字符串
🔎 Python | 蓝桥杯进阶第二卷——贪心
💝 Python | 蓝桥杯进阶第三卷——动态规划
✈️ Python | 蓝桥杯进阶第四卷——图论
🌞 Python | 蓝桥杯进阶第五卷——数论
💎 Python | 蓝桥杯进阶第六卷——搜索
Python | 蓝桥杯系列文章总结+经典例题重做
接下来我们从上面的文章中选取一定量的题目,进行回顾总结,并在原先基础上进行改进。
☔ 蚂蚁感冒
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
长 100
厘米的细长直杆子上有 n
只蚂蚁。它们的头有的朝左,有的朝右。
每只蚂蚁都只能沿着杆子向前爬,速度是 1
厘米/秒。
当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。
这些蚂蚁中,有 1
只蚂蚁感冒了。并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。
请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。
输入描述:
第一行输入一个整数 n(1 < n < 50)
, 表示蚂蚁的总数。
接着的一行是n个用空格分开的整数 Xi(-100 < Xi < 100)
, Xi
的绝对值,表示蚂蚁离开杆子左边端点的距离。正值表示头朝右,负值表示头朝左,数据中不会出现 0
值,也不会出现两只蚂蚁占用同一位置。其中,第一个数据代表的蚂蚁感冒了。
输出描述:
要求输出 1
个整数,表示最后感冒蚂蚁的数目。
样例输入:
5
-10 8 -20 12 25
样例输出:
3
解题思路
首先我们要计算两个值:left
和 right
:
left
表示第一只感冒蚂蚁的左侧, 头向右蚂蚁的数量
right
表示第一只感冒蚂蚁的右侧, 头向左蚂蚁的数量
其次,我们经过分析得到,感冒要传染,只能通过相遇,而不能通过追击。
对于第一只感冒的蚂蚁,我们分为两种情况讨论:
- 头向左
-
- 若其左侧有头向右的蚂蚁,则左侧的
left
只蚂蚁会依次逐个传染;而第一只感冒蚂蚁会转头,会使右侧的right
只蚂蚁依次逐个传染;
- 若其左侧有头向右的蚂蚁,则左侧的
-
- 若其左侧没有头向右的蚂蚁,则不会有蚂蚁传染。
- 头向右
-
- 若其右侧有头向左的蚂蚁,则右侧的
right
只蚂蚁会依次逐个传染;而第一只感冒蚂蚁会转头,会使左侧的left
只蚂蚁依次逐个传染;
- 若其右侧有头向左的蚂蚁,则右侧的
-
- 若其右侧没有头向左的蚂蚁,则不会有蚂蚁传染。
参考代码
n = int(input())
dis = list(map(int, input().split()))
# k 为第一只感冒蚂蚁距离左边的距离
k = abs(dis[0])
# left 表示第一只感冒蚂蚁的左侧, 头向右蚂蚁的数量
# right 表示第一只感冒蚂蚁的右侧, 头向左蚂蚁的数量
left = right = 0
# 计算最终感冒蚂蚁
for i in dis[1:]:
# 对于第一只感冒蚂蚁的左侧, 如果头向右
if 0 < i < k:
left += 1
# 对于第一只感冒蚂蚁的右侧, 如果头向左
if i < 0 and abs(i) > k:
right += 1
# 如果第一只感冒的蚂蚁头向左, 且 left 不为 0
# 或 第一只感冒的蚂蚁头向右, 且 right 不为 0
if (dis[0] < 0 and left != 0) or (dis[0] > 0 and right != 0):
print(left + right + 1)
else:
print(1)
❄️ Sine之舞
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
最近FJ为他的奶牛们开设了数学分析课,FJ知道若要学好这门课,必须有一个好的三角函数基本功。所以他准备和奶牛们做一个“Sine之舞”的游戏,寓教于乐,提高奶牛们的计算能力。
不妨设
A
n
=
s
i
n
(
1
–
s
i
n
(
2
+
s
i
n
(
3
–
s
i
n
(
4
+
.
.
.
s
i
n
(
n
)
)
.
.
.
)
An=sin(1–sin(2+sin(3–sin(4+...sin(n))...)
An=sin(1–sin(2+sin(3–sin(4+...sin(n))...)
S
n
=
(
.
.
.
(
A
1
+
n
)
A
2
+
n
−
1
)
A
3
+
.
.
.
+
2
)
A
n
+
1
Sn=(...(A1+n)A2+n-1)A3+...+2)An+1
Sn=(...(A1+n)A2+n−1)A3+...+2)An+1
FJ想让奶牛们计算 Sn
的值,请你帮助FJ打印出 Sn
的完整表达式,以方便奶牛们做题。
输入描述:
仅有一个数:N < 201
。
输出描述:
请输出相应的表达式 Sn
,以一个换行符结束。输出中不得含有多余的空格或换行、回车符。
样例输入:
3
样例输出:
((sin(1)+3)sin(1-sin(2))+2)sin(1-sin(2+sin(3)))+1
解题思路
本题可以分别构造两个函数,将两者表示即可,其中对于 An
,其函数表示为:
def A(n):
# res 用来记录 An 结果
res = ''
for i in range(1, n+1):
res += 'sin(' + str(i)
if i != n:
if i%2 != 0:
res += '-'
else:
res += '+'
# 结尾的 n 个反括号
res += ')' * n
return res
而对于 Sn
有:
def S(n):
# res 用来记录 Sn 的结果
res = ''
# 开头的 n-1 个正括号
res += '(' * (n-1)
for i in range(1, n+1):
res += str(A(i)) + '+' + str(n-i+1)
if i != n:
res += ')'
return res
之后就直接利用这两个函数即可。
参考代码
def A(n):
# res 用来记录 An 结果
res = ''
for i in range(1, n+1):
res += 'sin(' + str(i)
if i != n:
if i%2 != 0:
res += '-'
else:
res += '+'
# 结尾的 n 个反括号
res += ')' * n
return res
def S(n):
# res 用来记录 Sn 的结果
res = ''
# 开头的 n-1 个正括号
res += '(' * (n-1)
for i in range(1, n+1):
res += str(A(i)) + '+' + str(n-i+1)
if i != n:
res += ')'
return res
if __name__ == '__main__':
n = int(input())
print(S(n))
🐯 完美的代价
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
回文串,是一种特殊的字符串,它从左往右读和从右往左读是一样的。小龙龙认为回文串才是完美的。现在给你一个串,它不一定是回文的,请你计算最少的交换次数使得该串变成一个完美的回文串。
交换的定义是:交换两个相邻的字符
例如:对于 mamad
:
第一次交换 ad
: mamda
第二次交换 md
: madma
第三次交换 ma
: madam
(回文!完美!)
输入描述:
第一行是一个整数 N
,表示接下来的字符串的长度(N < = 8000)
第二行是一个字符串,长度为 N
,只包含小写字母
输出描述:
如果可能,输出最少的交换次数。
否则输出 Impossible
样例输入:
5
mamad
样例输出:
3
解题思路
本题利用双指针,其中头指针 head
从前向后遍历,尾指针 tail
从后向前遍历。
退出循环的条件:
- 字符串长度为偶数,但是有频数为奇数的字母
- 字符串长度为奇数,但是有多于
1
个的频数为奇数的字母
具体思路见参考代码。
参考代码
n = int(input())
s = list(input())
count = 0 # 记录交换次数
flag = 0 # 标记是否有字母频数为奇数
res = True # 标记是否为Impossible(是Impossible置为False)
# 双指针
L = n - 1 # 头指针搜索范围,到倒数第2个
for head in range(L):
# 对于每一个s[head],用尾指针去搜索是否有相同的字母s[tail]
for tail in range(L, head - 1, -1):
# 指针tail搜索完毕,没有发现s[head] == s[tail]
# 说明该字母频数为奇数 1,且该字母在回文序列中一定在中间
if head == tail:
# 如果字符串长度为偶数
# 字符串长度为奇数,但是已经有 flag == 1
if n%2 == 0 or flag == 1:
res = False
break
flag = 1
# (n//2 - head) 为将该字母移动到中间位置的交换次数
count += n//2 - head
# 发现了 s[head] == s[tail]
elif s[head] == s[tail]:
# 交换位置
for i in range(tail, L):
s[i], s[i+1] = s[i+1], s[i]
count += 1
L -= 1
break
# 如果是impossible,退出外层循环
if res == False:
break
if res == False:
print('Impossible')
else:
print(count)
🐧 字符串展开
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
在初赛普及组的“阅读程序写结果”的问题中,我们曾给出一个字符串展开的例子:如果在输入的字符串中,含有类似于 d-h
或者 4-8
的字串,我们就把它当作一种简写,输出时,用连续递增的字母获数字串替代其中的减号,即,将上面两个子串分别输出为 defgh
和 45678
。在本题中,我们通过增加一些参数的设置,使字符串的展开更为灵活。具体约定如下:
- 遇到下面的情况需要做字符串的展开:在输入的字符串中,出现了减号
-
,减号两侧同为小写字母或同为数字,且按照ASCII码的顺序,减号右边的字符严格大于左边的字符。 - 参数
p1
:展开方式。p1=1
时,对于字母子串,填充小写字母;p1=2
时,对于字母子串,填充大写字母。这两种情况下数字子串的填充方式相同。p1=3
时,不论是字母子串还是数字字串,都用与要填充的字母个数相同的星号*
来填充。 - 参数
p2
:填充字符的重复个数。p2=k
表示同一个字符要连续填充k
个。例如,当p2=3
时,子串d-h
应扩展为deeefffgggh
。减号两边的字符不变。 - 参数
p3
:是否改为逆序:p3=1
表示维持原来顺序,p3=2
表示采用逆序输出,注意这时候仍然不包括减号两端的字符。例如当p1=1
、p2=2
、p3=2
时,子串d-h
应扩展为dggffeeh
。 - 如果减号右边的字符恰好是左边字符的后继,只删除中间的减号,例如:
d-e
应输出为de
,3-4
应输出为34
。如果减号右边的字符按照 ASCII码的顺序小于或等于左边字符,输出时,要保留中间的减号,例如:d-d
应输出为d-d
,3-1
应输出为3-1
。
输入描述:
输入包括两行:
第 1
行为用空格隔开的 3
个正整数,一次表示参数 p1
,p2
,p3
。
第 2
行为一行字符串,仅由数字、小写字母和减号 -
组成。行首和行末均无空格。
数据规模和约定
100%的数据满足:1<=p1<=3
,1<=p2<=8
,1<=p3<=2
。字符串长度不超过 100
输出描述:
输出只有一行,为展开后的字符串.
样例输入:
1 2 1
abcs-w1234-9s-4zz
样例输出:
abcsttuuvvw1234556677889s-4zz
解题思路
- 符号
-
只会在s[1:len(s)-1]
之间(s
为输入字符串),因此要在这个范围内遍历,找到符号-
并将其展开; - 满足展开的条件,即要判断左右两边的字符的ASCII码关系;
- 之后根据
p3->p1->p2
的判断顺序对展开字符进行修改。
具体见代码。
参考代码
p1, p2, p3 = map(int, input().split())
s = input()
res = s[0]
# '-' 只会在s[1:len(s)-1]中间,因此s的首尾不需要变化
for i in range(1, len(s)-1):
left, mid, right = s[i-1], s[i], s[i+1]
# 遇到 '-' 并且左右字符满足如下条件:
# 条件1:left<right
# 条件2:(left>='0' and right<='9') or (left>='a' and right<='z')
if mid == '-' and left < right and ((left >= '0' and right <= '9') or (left >= 'a' and right <= 'z')):
## 判断顺序:p3 -> p1 -> p2
## p3
if p3 == 1:
# 顺序
index_j = [j for j in range(ord(left)+1, ord(right))]
else:
# 逆序
index_j = [j for j in range(ord(right)-1, ord(left), -1)]
## p1
for j in index_j:
# chr()转为字符
tmp = chr(j)
if p1 == 2:
# 变为大写
tmp = tmp.upper()
elif p1 == 3:
# 变为 '*'
tmp = '*'
## p2
# 重复 p2 次
res += tmp * p2
else:
res += mid
# 尾巴
res += s[-1]
print(res)
🌴 Huffman树
题目:
时间限制:
3s
内存限制:
192MB
题目描述:
Huffman树在编码中有着广泛的应用。在这里,我们只关心Huffman树的构造过程。
给出一列数 {pi}={p0, p1, …, pn-1}
,用这列数构造Huffman树的过程如下:
-
找到
{pi}
中最小的两个数,设为pa
和pb
,将pa
和pb
从{pi}
中删除掉,然后将它们的和加入到{pi}
中。这个过程的费用记为pa + pb
。 -
重复步骤1,直到
{pi}
中只剩下一个数。
在上面的操作过程中,把所有的费用相加,就得到了构造Huffman树的总费用。
本题任务:对于给定的一个数列,现在请你求出用该数列构造Huffman树的总费用。
例如,对于数列 {pi}={5, 3, 8, 2, 9}
,Huffman树的构造过程如下:
-
找到
{5, 3, 8, 2, 9}
中最小的两个数,分别是2
和3
,从{pi}
中删除它们并将和5
加入,得到{5, 8, 9, 5}
,费用为5
。 -
找到
{5, 8, 9, 5}
中最小的两个数,分别是5
和5
,从{pi}
中删除它们并将和10
加入,得到{8, 9, 10}
,费用为10
。 -
找到
{8, 9, 10}
中最小的两个数,分别是8
和9
,从{pi}
中删除它们并将和17
加入,得到{10, 17}
,费用为17
。 -
找到
{10, 17}
中最小的两个数,分别是10
和17
,从{pi}
中删除它们并将和27
加入,得到{27}
,费用为27
。 -
现在,数列中只剩下一个数
27
,构造过程结束,总费用为5+10+17+27=59
。
输入描述:
输入的第一行包含一个正整数 n(n< =100)
。
接下来是 n
个正整数,表示 p0, p1, …, pn-1
,每个数不超过 1000
。
输出描述:
输出用这些数构造Huffman树的总费用。
样例输入:
5
5 3 8 2 9
样例输出:
59
解题思路
排序后循环 n-1
次,每次选出前两个(最小值),计算其和后,再加入列表中,并将这两个最小值删除。
参考代码
n = int(input())
nums = list(map(int, input().split()))
res = 0
nums.sort()
for i in range(n-1):
total = nums[0] + nums[1]
nums.append(total)
res += total
nums.pop(0)
nums.pop(0)
nums.sort()
print(res)
🍁 2^k进制数
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
设
r
r
r 是个
2
k
2^k
2k 进制数,并满足以下条件:
- r r r 至少是个 2 2 2 位的 2 k 2^k 2k 进制数。
- 作为 2 k 2^k 2k 进制数,除最后一位外, r r r 的每一位严格小于它右边相邻的那一位。
- 将 r r r 转换为 2 2 2 进制数 q q q 后,则 q q q 的总位数不超过 w w w。
在这里,正整数 k ( 1 ≤ k ≤ 9 ) k(1≤k≤9) k(1≤k≤9)和 w ( k < w ≤ 30000 ) w(k<w≤30000) w(k<w≤30000) 是事先给定的。
问:满足上述条件的不同的
r
r
r 共有多少个?
我们再从另一角度作些解释:设
S
S
S 是长度为
w
w
w 的 01字符串(即字符串
S
S
S 由
w
w
w 个0
或1
组成),
S
S
S 对应于上述条件中的
q
q
q。将
S
S
S 从右起划分为若干个长度为
k
k
k 的段,每段对应一位
2
k
2^k
2k 进制的数,如果
S
S
S 至少可分成
2
2
2 段,则
S
S
S 所对应的二进制数又可以转换为上述的
2
k
2^k
2k 进制数
r
r
r。
例:设
k
=
3
,
w
=
7
k=3,w=7
k=3,w=7。则
r
r
r 是个八进制数
(
2
3
=
8
)
(2^3=8)
(23=8)。由于
w
=
7
w=7
w=7,长度为 7 的 01 字符串按 3位一段分,可分为 3 段(即1,3,3
,左边第一段只有一个二进制位),则满足条件的八进制数有:
2
位数:
高位为1
:6
个(即 12,13,14,15,16,17
),高位为 2
:5
个,…,高位为6
:1
个(即67
)。共 6+5+…+1=21
个。
3
位数:
高位只能是1
,第 2
位为 2
:5
个(即123,124,125,126,127
),第2
位为3
:4
个,…,第 2
位为 6
:1
个(即 167
)。共5+4+…+1=15
个。
所以,满足要求的
r
r
r 共有 36
个。
输入描述:
只有1行,为两个正整数,用一个空格隔开:
k
k
k
w
w
w
输出描述:
1行,是一个正整数,为所求的计算结果,即满足条件的不同的
r
r
r 的个数(用十进制数表示),要求最高位不得为0,各数字之间不得插入数字以外的其他字符(例如空格、换行符、逗号等)。
(提示:作为结果的正整数可能很大,但不会超过200位)
样例输入:
3 7
样例输出:
36
解题思路
确定 dp
数组及其下标的含义:
dp[i][j]
表示有 (i+1)
位数字,最高位数字是 j
时满足条件的数字个数。
确定递推公式:
若 j!=0
:dp[i][j] = dp[i-1][j+1]+dp[i-1][j+2]+dp[i-1][j+3]······
也就是说最高位不是0的时候,满足条件的数字个数是,第二高位的数字大于最高位的数字的情况的和。
若j==0
:dp[i][j] = dp[i-1][j]+dp[i-1][j+1]+dp[i-1][j+2]+dp[i-1][j+3]······
如果最高位是 0
,那么第二高位数字则可以取0。也就比上一个情况多加了个 dp[i-1][j]
。
初始化 dp
数组:
dp[0][j]=1
即只有一位数字时只有一种情况
确定遍历顺序:
顺序遍历即可。
此外注意最终结果的计算和处理,具体见代码。
参考代码
import math
k, w = map(int, input().split())
# rows, cols 为dp数组的行数和列数
rows, cols = w//k+1, 2**k
# i 是 2^k 进制下数字的位数
# j 是 每一位上可以取到的值
# dp[i][j] 表示有 (i+1) 位数字,最高位数字是 j 时满足条件的数字个数
dp = [[0 for j in range(cols)] for i in range(rows)]
# 初始化 res,为结果
res = 0
# dp 数组初始化
# dp[0][j] 表示有 1 位数字的情况
for j in range(cols):
dp[0][j] = 1
# 递推
for i in range(1, rows):
for j in range(cols):
dp[i][j] = sum(dp[i-1][j+1:])
if j == 0:
dp[i][j] += dp[i-1][j]
# 计算最后的结果
if w%k == 0:
# 此时最高位可以取 0 到 cols, 即最后一行所有情况都可以用
res = sum(dp[-1])
else:
# 此时最高位只能取 0 到 2**(w%k)-1
res = sum(dp[-1][:2**(w%k)])
# 题目中要求位数 >= 2
# 需要减去只有一位数的情况
if w/k <= 1:
res = 0
else:
res -= 2**k
print(res)
🌏 卡勒沃夫之弱水路三千
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
锦瑟年华谁与度 莫问情归处 只影向斜阳 剑吼西风 欲把春留驻
天涯芳草无归路 回首花无数 解语自销魂 弱袂萦春 尘缘不相误
…
在卡勒沃夫充满文学杀伤力的声音中,身处紫荆2号楼202B的四位远近高低各不同的室友纷纷回忆起了各自波澜起伏的过去,并对长在百草园,邻有百花谷的现状表达了各自的见解。
某Q:" …我小学就开窍了…她的父母说我很好,但是…今天又和北林的联系了…"
某X:" …差点就成了,结果到学校了…这个方法放假了我去对我的同桌用!…"
某W:" …" (千言万语不言中,有大量的故事等待考古)
某Z:" …为了来清华…咱们审美观不一样,不会抢…"
…
卡勒沃夫在这个不朽的夜话中搜集出了某人零散的历任女友资料,为了强迫某人将他出的题目的标程交出,现在卡勒沃夫需要一个能将这些零散信息整合起来的程序。
输入描述:
第一行为一个不超过 5
的整数 T
,表示数据的组数。之后每组数据的一行为一个不超过 100
的整数 n
。之后 n
行每行有两个用单个空格隔开的字符串(每个字符串只有英文大小写字母,长度不超过 10
),为两位 mm
的名字。每行第一个 mm
先于第二个 mm
成为某人的女友。
在这里我们假装诅咒某人不会同时被两个或两个以上 mm
泡,某个 mm
抛弃了某人后不会再吃回头草,同时卡勒沃夫深邃的洞察力使得他收集到了充足的信息以确定某人女友的先后顺序。
在小数据组中出现的人物不超过 13
个.
输出描述:
输出 T
行,每行对应一组数据,并按照 mm
们从先到后成为某人女友的顺序输出她们的名字,各个名字间用一个空格隔开。
样例输入:
2
2
RY Unknown
YSZ RY
3
tomorrow yestoday
tomorrow today
today yestoday
样例输出:
YSZ RY Unknown
tomorrow today yestoday
解题思路
本题思路是拓扑排序。
具体思路见参考代码代码和注释。
参考代码
from collections import defaultdict
"""
defaultdict 是 collections 模块中的一个类。
它继承自 Python 内置的 dict 类
但是与 dict 类不同的是,defaultdict 会自动为访问的不存在的键赋一个默认值。
这样在使用 defaultdict 时就不需要像在使用 dict 时那样先判断一个键是否存在,再进行对应的操作。
"""
class Graph:
def __init__(self):
# 每个键对应的值为一个列表,用来存储该键值作为起点对应的终点
self.graph = defaultdict(list)
def addEdge(self, u, v):
# 添加一条以 u 为起点,v 为终点的边
self.graph[u].append(v)
# 拓扑排序函数
def topoLogicalSortUtil(self, key, visited, stack):
# 标记该结点为已访问
visited[key] = True
for i in self.graph[key]:
if visited[i] == False:
self.topoLogicalSortUtil(i, visited, stack)
# 用一个栈来保存排序结点
stack.append(key)
def topoLogicalSort(self, dic):
visited = dic.copy()
stack = []
for key in dic.keys():
# 判断该结点是否被访问过
if visited[key] == False:
# 如果没有被访问过
self.topoLogicalSortUtil(key, visited, stack)
# 栈是先进后出,因此需要倒序输出
for i in stack[::-1]:
print(i, end=' ')
print()
t = int(input())
while t:
# dic 用来保存结点状态:是否被访问过
# start, end 分别存储起点和终点
dic, start, end = {}, [], []
n = int(input())
for i in range(n):
a, b = input().split()
start.append(a)
end.append(b)
# grils 利用集合去重,可以得到所有结点(即所有前女友名字)
grils = set(start + end)
# 创建 Graph 类
gf_graph = Graph()
# 添加边
for i in zip(start, end):
gf_graph.addEdge(i[0], i[1])
# 初始化结点状态
for i in grils:
dic.update({i: False})
# 拓扑排序
gf_graph.topoLogicalSort(dic)
t -= 1
🏀 盾神与砝码称重
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
有一天,他在宿舍里无意中发现了一个天平!这个天平很奇怪,有n个完好的砝码,但是没有游码。盾神为他的发现兴奋不已!于是他准备去称一称自己的东西。他准备好了 m
种物品去称。神奇的是,盾神一早就 知道这 m
种物品的重量,他现在是想看看这个天平能不能称出这些物品出来。但是盾神稍微想了1秒钟以后就觉得这个问题太无聊了,于是就丢给了你。
输入描述:
第一行为两个数,n
和 m
。
第二行为 n
个数,表示这 n
个砝码的重量。
第三行为 m
个数,表示这 m
个物品的重量。
数据规模和约定
1 <= n <= 24, 1 <= m <= 10
.
输出描述:
输出 m
行,对于第 i
行,如果第 i
个物品能被称出,输出 YES
否则输出 NO
。
样例输入:
4 2
1 2 4 8
15 16
样例输出:
YES
NO
解题思路
深度优先搜索。
注意每个砝码有三种状态:
- 砝码和物品放在不同侧;
- 不使用该砝码
- 砝码和物品放在同一侧
对于每一个称重物品,逐个考虑砝码的三种状态,进行搜索。
具体见参考代码和注释。
参考代码
# 深度优先搜索
def dfs(num, res):
global weight, tag, w01
# 退出条件:达到物品的重量 -- 成功
if res == weight:
tag = True
return
# 退出条件:没有砝码可用 -- 失败
elif num < 0:
return
else:
## 搜索下一个砝码
# 将这个砝码放在物品的不同侧
dfs(num - 1, res + int(w01[num]))
# 不使用第 i 个砝码
dfs(num - 1, res)
# 将这个砝码和物品放在同一侧
dfs(num - 1, res - int(w01[num]))
if __name__ == '__main__':
# 输入
n, m = map(int, input().split())
# w01, w02 分别为砝码和物品重量
w01 = list(map(int, input().split()))
w02 = list(map(int, input().split()))
# 对于每一个物品
for i in w02:
weight = i
tag = False
dfs(n-1, 0)
# 输出结果
if tag:
print('YES')
else:
print('NO')
🍏 麦森数
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
任何一个正整数都可以用 2
的幂次方表示。例如:
137=2^7+2^3+2^0
同时约定方次用括号来表示,即 ab
可表示为 a(b)
。
由此可知,137
可表示为:
2(7)+2(3)+2(0)
进一步:7= 2^2+2+2^0
(2^1
用 2
表示)
3=2+2^0
所以最后 137
可表示为:
2(2(2)+2+2(0))+2(2+2(0))+2(0)
又如:
1315=2^10+2^8+2^5+2+2^0
所以 1315
最后可表示为:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入描述:
输入包含一个正整数 N(N<=20000)
,为要求分解的整数。
输出描述:
程序输出包含一行字符串,为符合约定的 n
的 0
,2
表示(在表示中不能有空格)
样例输入:
1315
样例输出:
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
解题思路
可以通过每个数的二进制来得到其二次幂分解:
比如样例中的 1315
,其对应的二进制数为:0b10100100011
其中 1
对应的位置由高到低为:11 9 6 2 1
因此其对应二次幂分解为:1315 = 2^10 + 2^8 + 2^5 +2 ^1 + 2^0
对于 0
次幂和 1
次幂特别处理,而对于高次幂,通过递归调用。
注意:要从高次幂开始处理。
具体见参考代码及其注释。
参考代码
def trans(n):
# 转为 2 进制后再处理
tmp = list(bin(n))
# 去除 2 进制前面的 0b
n = tmp[2:]
# 转换为整数
n = [int(i) for i in n]
res = ''
for i in range(1, len(n) + 1):
if n[len(n) - i]:
if len(n) - i == 0:
# 处理 0 次幂
res += '2(0)+'
elif len(n) - i == 1:
# 处理 1 次幂
res += '2+'
else:
# 其余情况,递归调用
res += '2(' + trans(len(n) - i) + ')+'
# 最后res会有一个 '+' 需要去除
return res[:-1]
if __name__ == '__main__':
n = int(input())
print(trans(n))
🎁 危险系数
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
抗日战争时期,冀中平原的地道战曾发挥重要作用。
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数 DF(x,y)
:
对于两个站点 x
和 y (x != y)
, 如果能找到一个站点 z
,当 z
被敌人破坏后,x
和 y
不连通,那么我们称 z
为关于 x,y
的关键点。相应的,对于任意一对站点 x
和 y
,危险系数 DF(x,y)
就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
输入描述:
输入数据第一行包含 2
个整数 n(2 <= n <= 1000), m(0 <= m <= 2000)
,分别代表站点数,通道数;
接下来 m
行,每行两个整数 u,v (1 < = u, v < = n; u != v)
代表一条通道;
最后 1
行,两个数 u,v
,代表询问两点之间的危险系数 DF(u, v)
。
输出描述:
一个整数,如果询问的两点不连通则输出 -1
.
样例输入:
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
样例输出:
2
解题思路
深度优先搜索,对于每条从 u
到 v
的路径,对于路径上经过的每个结点 i
,其到达 v
的路径数 path[i]
都记 +1
,搜索结束后若 path[i]==path[v]
则说明这个点为关键点。
具体思路见参考代码及其注释。
参考代码
# 深度优先搜索
def dfs(u, v):
global path
# 退出条件
if u == v:
# 当访问到最后的站点 end
for i in range(n):
# 遍历所有站点
if visited[i] == True:
# 如果这个站点在这条路线上
path[i] += 1
return
for i in range(n):
if mat[u][i] == 1 and not visited[i]:
# 当 start 和 i 两站点连通
# 且站点 i 未被访问
visited[i] = True
# 递归
dfs(i, v)
# 回溯
visited[i] = False
if __name__ == '__main__':
n, m = map(int, input().split())
# mat 为邻接矩阵
mat = [[0 for j in range(n)] for i in range(n)]
for i in range(m):
a, b = map(int, input().split())
mat[a-1][b-1] = 1
mat[b-1][a-1] = 1
# u, v 两个节点
u, v = map(int, input().split())
u, v = u-1, v-1
# visited[i] 表示站点 i 是否被访问
visited = [False for i in range(n)]
# path[i] 表示经过站点 i 到终点 v 的路径数
path = [0 for i in range(n)]
dfs(u, v)
# path[v]为从 u 到 v 的路径数
# 当其他站点也为这个数时,说明该站点为关键点
print(path.count(path[v]) - 1)
# 这里只需要 -1 是表示 v 结点
# 而不用考虑 u 结点,是因为我们设置邻接矩阵时有 mat[i][i] = 0
💡 2n皇后问题
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
给定一个 n*n
的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入 n
个黑皇后和 n
个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n
小于等于 8
。
输入描述:
输入的第一行为一个整数 n
,表示棋盘的大小。 n
小于等于 8
接下来 n
行,每行 n
个 0
或 1
的整数,如果一个整数为 1
,表示对应的位置可以放皇后,如果一个整数为 0
,表示对应的位置不可以放皇后。
输出描述:
输出一个整数,表示总共有多少种放法。
样例输入:
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出:
2
解题思路
根据题意我们得到:每行必然有一个黑皇后和一个白皇后。
我们的思路是:先将各行的黑皇后放好,再将各行的白皇后放好。
定义 s
为皇后状态,s=2
为黑皇后,s=3
为白皇后。
即对于 cell
表示各结点状态:有 0
表示不可以放皇后,1
表示可以放皇后,2
表示黑皇后,3
表示白皇后。
定义 check(row, col, s, cell)
函数,用来判断坐标 (row, col)
放 s
对应的皇后是否可行。
定义深度优先搜索函数 dfs(row, n, s, cell)
搜索第 row
行(最多有 n
行)是否可以放 s
对应的皇后。
参考代码
n = int(input())
cell = [list(map(int, input().split())) for i in range(n)]
count = 0
# check 函数,判断皇后放的位置是否满足条件
def check(row, col, s, cell):
# 检查对应列是否有 s
for i in range(row-1, -1, -1):
if cell[i][col] == s:
return False
# 检查左对角线
r = row - 1
c = col - 1
while r >= 0 and c >= 0:
if cell[r][c] == s:
return False
r -= 1
c -= 1
# 检查右对角线
r = row - 1
c = col + 1
while r >= 0 and c < n:
if cell[r][c] == s:
return False
r -= 1
c += 1
return True
# dfs 函数
def dfs(row, n, s, cell):
global count
# 判断是否到最后一行
# 这里是 n 表示,row == n-1 时已经搜索完毕
# 再次递归时有 row == n,这是说明已经到底
if row == n:
# 若为黑皇后
if s == 2:
# 则接下来从第一行开始放白皇后
dfs(0, n, 3, cell)
# 若为白皇后
# 此时黑白皇后已经全部放完
# 退出
if s == 3:
count += 1
return
# 遍历每行
for col in range(n):
# 如果 cell[row][col] 不能再放皇后
if cell[row][col] != 1:
continue
# 如果该结点可以放皇后
if check(row, col, s, cell):
# 改变结点状态
cell[row][col] = s
# 递归搜索下一行
dfs(row+1, n, s, cell)
# 回溯
cell[row][col] = 1
if __name__ == '__main__':
dfs(0, n, 2, cell)
print(count)
🚲 学霸的迷宫
题目:
时间限制:
1s
内存限制:
128MB
题目描述:
学霸抢走了大家的作业,班长为了帮同学们找回作业,决定去找学霸决斗。但学霸为了不要别人打扰,住在一个城堡里,城堡外面是一个二维的格子迷宫,要进城堡必须得先通过迷宫。因为班长还有妹子要陪,磨刀不误砍柴功,他为了节约时间,从线人那里搞到了迷宫的地图,准备提前计算最短的路线。可是他现在正向妹子解释这件事情,于是就委托你帮他找一条最短的路线。
输入描述:
第一行两个整数 n
, m
,为迷宫的长宽。
接下来n行,每行 m
个数,数之间没有间隔,为 0
或 1
中的一个。0
表示这个格子可以通过,1
表示不可以。假设你现在已经在迷宫坐标 (1,1)
的地方,即左上角,迷宫的出口在 (n,m)
。每次移动时只能向上下左右 4
个方向移动到另外一个可以通过的格子里,每次移动算一步。数据保证 (1,1)
,(n,m)
可以通过。
输出描述:
第一行一个数为需要的最少步数 K
。
第二行 K
个字符,每个字符 ∈{U,D,L,R}
,分别表示上下左右。如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。
样例输入:
3 3
001
100
110
样例输出:
4
RDRD
解题思路
本题利用广度优先搜索。
注意题目中要求:
如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。
因此我们搜索的顺序是 D, L, R, U
,即:下左右上。
思路与上文中穿越雷区相同。
参考代码
# 广度优先搜索
n, m = map(int, input().split())
cell = [list(map(int, input())) for i in range(n)]
# visited用来记录结点访问状态
visited = [[False for j in range(m)] for i in range(n)]
visited[0][0] = True
# dx, dy 表示 下左右上 方向的坐标变动
d = ['D', 'L', 'R', 'U']
dx = [1, 0, 0, -1]
dy = [0, -1, 1, 0]
# res 用来记录当前搜索层结点的结果
# res[0][0] 表示横坐标
# res[0][1] 表示纵坐标
# res[0][2] 表示到达该结点的移动次数
# res[0][3] 表示到达该结点的路径
res = [[0, 0, 0, []]]
while res:
# 终止条件:到达目标结点
if (res[0][0] == n-1) and (res[0][1] == m-1):
print(res[0][2])
print(''.join(res[0][3]))
# 遍历四个方向
for i in range(4):
new_x = res[0][0] + dx[i]
new_y = res[0][1] + dy[i]
new_path = res[0][3] + [d[i]]
# 如果新坐标不超过边界
if (0 <= new_x <= n-1) and (0 <= new_y <= m-1):
# 如果该结点没有被访问过,且可以通过
if (not visited[new_x][new_y]) and cell[res[0][0]][res[0][1]] == 0:
res.append([new_x, new_y, res[0][2]+1, new_path])
visited[new_x][new_y] = True
# 该结点搜索完毕
res.pop(0)