测试链接:https://www.dotcpp.com/oj/train/1021/
成绩分析 100🏆
【题目描述】
小蓝给学生们组织了一场考试,卷面总分为 100 分,每个学生的得分都是一个 0 到100 的整数。请计算这次考试的最高分、最低分和平均分。
【输入格式】
输入的第一行包含一个整数 n,表示考试人数。
接下来n 行,每行包含一个0 至100 的整数,表示一个学生的得分。
【输出格式】
输出三行。
第一行包含一个整数,表示最高分。
第二行包含一个整数,表示最低分。
第三行包含一个实数,四舍五入保留正好两位小数,表示平均分。
【样例】
输入 | 输出 |
7 80 92 56 74 88 99 10 | 99 10 71.29 |
【评测用例规模与约定】
50% | 1 ≤ n ≤ 100 |
100% | 1 ≤ n ≤10000 |
【解析及代码】
num = int(input())
score = [int(input()) for _ in range(num)]
print(max(score))
print(min(score))
print(f"{sum(score) / num:.2f}")
回文日期 100🏆
【题目描述】
2020 年春节期间,有一个特殊的日期引起了大家的注意:2020年2月2日。因为如果将这个日期按“yyyymmdd” 的格式写成一个8 位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期。
有人表示 20200202 是“千年一遇” 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文日期:20211202 即2021年12月2日。
也有人表示 20200202 并不仅仅是一个回文日期,还是一个 ABABBABA 型的回文日期。对此小明也不认同,因为大约100 年后就能遇到下一个 ABABBABA 型的回文日期:21211212 即2121 年12 月12 日。算不上“千年一遇”,顶多算“千年两遇”。
给定一个8 位数的日期,请你计算该日期之后下一个回文日期和下一个ABABBABA型的回文日期各是哪一天。
【输入格式】
输入包含一个八位整数N,表示日期
【输出格式】
输出两行,每行1 个八位数。第一行表示下一个回文日期,第二行表示下一个ABABBABA 型的回文日期
【样例】
输入 | 输出 |
20200202 | 20211202 21211212 |
【评测用例规模与约定】
100% | 10000101 ≤ N ≤ 89991231 保证 N 是一个合法日期的8位数表示 |
【解析及代码】
定义 check_date 函数,使用 datetime 模块和 try 语句对日期的正确与否进行判断
然后使用一个 while 循环对两种日期进行搜索,对 result 中的 None 进行替换
date 对象的字符串是 2001-03-23 这种形式,使用字符串的 replace 方法把“-”去掉,再输出就好了
from datetime import date
start = input()
year, month, day = map(int, (start[:4], start[4: 6], start[6:]))
start = date(year, month, day)
def check_date(y, md):
m, d = map(int, (md[:2], md[2:]))
try:
date_ = date(y, m, d)
# 确保日期在起始日期之后
assert date_ > start
return date_
except:
return False
result = [None] * 2
while not all(result):
# 构建回文日期
md = str(year)[::-1]
# 确认是否是正确日期
date_ = check_date(year, md)
if date_:
# 普通回文日期未找到
if not result[0]: result[0] = date_
# ABABBABA 回文日期未找到, 判断年份是不是 ABAB
if not result[1] and year // 100 == year % 100: result[1] = date_
# 年份递增
year += 1
result = map(lambda d: str(d).replace('-', ''), result)
print(*result, sep='\n')
子串分值 100🏆
【题目描述】
对于一个字符串 S (长度 n),我们定义 S 的分值 f(S) 为 S 中恰好出现一次的字符个数。例如 f(”aba”) = 1,f(”abc”) = 3, f(”aaa”) = 0。
现在给定一个字符串 S (长度为 n),请你计算对于所有 S 的非空子串 S[i…j] (0 ≤ i ≤ j < n),f (S[i… j]) 的和是多少。
【输入格式】
输入一行包含一个由小写字母组成的字符串 S。
【输出格式】
输出一个整数表示答案。
【样例】
输入 | 输出 | 说明 |
ababc | 21 | a 1 |
【评测用例规模与约定】
20% | |
40% | |
50% | |
60% | |
100% | |
【解析及代码】
换个角度想这个问题,每个字符可以贡献多少分,也就是每个字符可以有效地出现在多少个子串里
如果给定 ababc,那么计算方法如下:
- i = 0:a 向左找相同字符索引为 -1 (找不到),向右找是 2,那么所在的有效子串数为 (0 - (-1)) × (2 - 0) = 2,包含 “a”、“ab”
- i = 1:b 向左找相同字符索引为 -1 (找不到),向右找是 3,那么所在的有效子串数为 (1 - (-1)) × (3 - 1) = 4,包含 “ab”、“b”、“ba”、“aba”
- i = 2:a 向左找相同字符索引为 0,向右找是 5 (找不到),那么所在的有效子串数为 (2 - 0) × (5 - 2) = 6,包含 “ba”、“a”、“ab”、“abc”、“bab”、“babc”
以此类推,可以得到最终答案是 2 + 4 + 6 + 4 + 5 = 21
利用 find 函数进行字符查找:字符串的 find 函数找不到时会返回 -1,左边界的溢出是 -1 不用管;右边界的溢出应该是 n,对 n+1 取模进行纠正
s, ans = input(), 0
for i in range(len(s)):
lp = s.rfind(s[i], 0, i)
rp = s.find(s[i], i + 1) % (len(s) + 1)
ans += (i - lp) * (rp - i)
print(ans)
作物杂交 64🏆
【题目描述】
作物杂交是作物栽培中重要的一步。已知有 N 种作物 (编号 1 至 N),第 i 种作物从播种到成熟的时间为 。作物之间两两可以进行杂交,杂交时间取两种中时间较长的一方。
如作物 A 种植时间为 5 天,作物 B 种植时间为 7 天,则 AB 杂交花费的时间为 7 天。作物杂交会产生固定的作物,新产生的作物仍然属于 N 种作物中的一种。
初始时,拥有其中 M 种作物的种子(数量无限,可以支持多次杂交)。同时可以进行多个杂交过程。
求问对于给定的目标种子,最少需要多少天能够得到。
如存在 4 种作物 ABCD,各自的成熟时间为 5 天、7 天、3 天、8 天。初始拥有 AB 两种作物的种子,目标种子为 D,已知杂交情况为 A×B→C,A×C→D。
则最短的杂交过程为:
第 1 天到第 7 天(作物 B 的时间),A×B→C。
第 8 天到第 12 天(作物 A 的时间),A×C→D。花费 12 天得到作物 D 的种子。
【输入格式】
输入的第 1 行包含4个整数 N,M,K,T,N 表示作物种类总数(编号 1 至 N),M 表示初始拥有的作物种子类型数量,K 表示可以杂交的方案数,T 表示目标种子的编号。第 2 行包含 N 个整数,其中第 i 个整数表示第 i 种作物的种植时间 (
)。第 3 行包含 M 个整数,分别表示已拥有的种子类型
(
),Kj两两不同。第 4 至 K+3 行,每行包含 3 个整数 A,B,C,表示第 A 类作物和第 B 类作物杂交可以获得第 C 类作物的种子。
【输出格式】
输出一个整数,表示得到目标种子的最短杂交时间
【样例】
输入 | 输出 | 说明 |
6 2 4 6 5 3 4 6 4 9 1 2 1 2 3 1 3 4 2 3 5 4 5 6 | 16 | 第1天至第5天,将编号1与编号2的作物杂交,得到编号3的作物种子 第6天至第10天,将编号1与编号3的作物杂交,得到编号4的作物种子 第6天至第9天,将编号2与编号3的作物杂交,得到编号5的作物种子 第11天至第16天,将编号4与编号5的作物杂交,得到编号6的作物种子 总共花费16天。 |
【评测用例规模与约定】
100% | 1 ≤ N ≤ 2000, 2 ≤ M ≤ N, 1 ≤ K ≤ 100000, 1 ≤ T ≤ N |
【解析及代码】
首先来分析一下样例,假设已经完成 A × B → C,现在要做 A × C → D
idx | A | B | C | D |
time | 0 | 0 | 7 | inf |
cost | 5 | 7 | 3 | 8 |
time 表示整个流程中得到种子所需时间 (已有的记为0),cost 代表种子成熟所需时间
我初看这道题时的思路是:time[D] = max( time[A] + cost[A], time[C] + cost[C] )
但是这个想法得到的结果是:time[D] = 10,明显与题意不符
正确的思路应该是:time[D] = max( time[A], time[C] ) + max( cost[A], cost[C] ) = 12
也就是:新种子 time = max(两种子 time) + max(两种子 cost)
定义树结点 Node,在 __init__ 方法里面把 cost 和 time 的信息录入,写个 search 方法用于计算 time
import math
class Node(list):
def __init__(self, cost, time):
super().__init__()
self.cost = cost
self.time = time
def search(self):
# 用于计算种子的 time
if math.isinf(self.time):
# 方案时间来源: 两种子 time 的最大值 + 两种子 cost 的最大值
self.time = min(max(a.search(), b.search()) + max(a.cost, b.cost)
for a, b in self)
return self.time
n, _, k, t = map(int, input().split())
t -= 1
# 种子成熟所需时间
cost = list(map(int, input().split()))
time = [math.inf] * n
# 得到种子所需时间
toidx = lambda n: int(n) - 1
for i in map(toidx, input().split()): time[i] = 0
# 初始化种子结点
seed = [Node(cost[i], time[i]) for i in range(n)]
for i in range(k):
a, b, c = map(toidx, input().split())
seed[c].append((seed[a], seed[b]))
print(seed[t].search())
游园安排 73🏆
【题目描述】
L 星球游乐园非常有趣,吸引着各个星球的游客前来游玩。小蓝是 L 星球
游乐园的管理员。为了更好的管理游乐园,游乐园要求所有的游客提前预约,小蓝能看到系统上所有预约游客的名字。每个游客的名字由一个大写英文字母开始,后面跟0 个或多个小写英文字母。游客可能重名。
小蓝特别喜欢递增的事物。今天,他决定在所有预约的游客中,选择一部分游客在上午游玩,其他的游客都在下午游玩,在上午游玩的游客要求按照预约的顺序排列后,名字是单调递增的,即排在前面的名字严格小于排在后面的名字。一个名字 A 小于另一个名字 B 是指:存在一个整数 i,使得 A 的前 i 个字母与 B 的前 i 个字母相同,且 A 的第 i+1 个字母小于 B 的第 i+1 个字母。(如果 A 不存在第 i + 1 个字母且 B 存在第 i + 1 个字母,也视为 A 的第 i + 1 个字母小于 B 的第 i + 1 个字母)作为小蓝的助手,你要按照小蓝的想法安排游客,同时你又希望上午有尽量多的游客游玩,请告诉小蓝让哪些游客上午游玩。如果方案有多种,请输出上午游玩的第一个游客名字最小的方案。如果此时还有多种方案,请输出第一个游客名字最小的前提下第二个游客名字最小的方案。如果仍然有多种,依此类推选择第三个、第四个……游客名字最小的方案。
【输入格式】
输入包含一个字符串,按预约的顺序给出所有游客的名字,相邻的游客名字之间没有字符分隔。
【输出格式】
按预约顺序输出上午游玩的游客名单,中间不加任何分隔字符
【样例】
输入 | 输出 |
WoAiLanQiaoBei | AiLanQiao |
【评测用例规模与约定】
20% | 输入的总长度不超过 20 个字母 |
50% | 输入的总长度不超过 300 个字母 |
70% | 输入的总长度不超过 10000 个字母 |
100% | 每个名字的长度不超过 10 个字母 输入的总长度不超过 1000000 个字母 |
【解析及代码】
这个题其实就是找最长递增序列,用正常的动态规划思路可以求出长度,但是还要记录这个序列、求字典序最小的,这就有点抠门
首先处理一下输入,用这个正则表达式可以匹配“Lan”、“Qiao”、“B”这种字符串,用findall函数找到所有的这种字符串即可完成分组
然后用一个 n 行 2 列的矩阵 dp 来记录状态,第 1 列表示前 n 行最长递增序列长度,第 2 列表示前 n 行最长递增序列 (字典序最小)
然后在做状态转移的时候严格控制字典序最小就好了,考虑达到最大长度的序列可能有多个,所以还要:找到最大长度,找到对应该长度的序列,取字典序最小的输出
import re
origin = re.findall(r'[A-Z][a-z]*', input())
dp = []
for i, ce in enumerate(origin):
# 初始长度 1, 初始字符串只有对应位置元素
cv, cs = 1, ce
for last in range(i):
(lv, ls), le = dp[last], origin[last]
# 比较字典序,满足递增条件
if le < ce and cv <= lv + 1:
cv = lv + 1
# 取字典序最小的字符串
cs = min(cs, ls + ce)
dp.append((cv, cs))
# 找到元素最长的长度
res = max(dp, key=lambda x: x[0])[0]
print(min(s for v, s in dp if v == res))
答疑 100🏆
【题目描述】
有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。
一位同学答疑的过程如下:
首先进入办公室,编号为 i 的同学需要 毫秒的时间。然后同学问问题老师解答,编号为 i 的同学需要
毫秒的时间。
答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可以忽略。最后同学收拾东西离开办公室,需要 毫秒的时间。一般需要 10 秒、20 秒或 30 秒,即
取值为 10000,20000 或 30000。一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。答疑从 0 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群里面发消息的时刻之和最小。
【输入格式】
输入第一行包含一个整数 n,表示同学的数量。
接下来 n 行,描述每位同学的时间。其中第 i 行包含三个整数 ,意义如上所述。
【输出格式】
输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少
【样例】
输入 | 输出 | 说明 |
3 10000 10000 10000 20000 50000 20000 30000 20000 30000 | 280000 | 按照 1, 3, 2 的顺序答疑 发消息的时间分别是 20000, 80000, 180000 |
【评测用例规模与约定】
30% | |
60% | |
100% |
【解析及代码】
记准备时间 s = s + a,离开时间 e,将 3 个时间段合并成 2 个
设 代表前 n 个同学的时间总和
假设当前已经有最优排序,第 n 位同学和第 n + 1 位同学对时间的贡献为:
现交换第 n 位同学与第 n + 1 位同学的位置,第n位同学和第 n + 1 位同学对时间的贡献为:
因为第一种是最优排序,所以满足 best < new,得:
所以依照 s + a + e 排序就可以得到正确排序了
n, greed = int(input()), []
for _ in range(n):
s, a, e = map(int, input().split())
greed.append((s + a + e, s + a))
# 按照三个时间的总和排序
greed.sort()
basic, res = 0, 0
for summary, pre in greed:
res += basic + pre
basic += summary
print(res)
蓝跳跳 50🏆
【题目描述】
小蓝制作了一个机器人,取名为蓝跳跳,因为这个机器人走路的时候基本靠跳跃。
蓝跳跳可以跳着走,也可以掉头。蓝跳跳每步跳的距离都必须是整数,每步可以跳不超过 k 的长度。由于蓝跳跳的平衡性设计得不太好,如果连续两次都是跳跃,而且两次跳跃的距离都至少是 p,则蓝跳跳会摔倒,这是小蓝不愿意看到的。
小蓝接到一个特别的任务,要在一个长为 L 舞台上展示蓝跳跳。小蓝要控制蓝跳跳从舞台的左边走到右边,然后掉头,然后从右边走到左边,然后掉头,然后再从左边走到右边,然后掉头,再从右边走到左边,然后掉头,如此往复。为了让观者不至于太无趣,小蓝决定让蓝跳跳每次用不同的方式来走。小蓝将蓝跳跳每一步跳的距离记录下来,按顺序排成一列,显然这一列数每个都不超过 k 且和是 L。这样走一趟就会出来一列数。如果两列数的长度不同,或者两列数中存在一个位置数值不同,就认为是不同的方案。
请问蓝跳跳在不摔倒的前提下,有多少种不同的方案从舞台一边走到另一边。
【输入格式】
输入一行包含三个整数 k, p, L
【输出格式】
输出一个整数,表示答案。答案可能很大,请输出答案除以 20201114 的余数
【样例】
输入 | 输出 |
3 2 5 | 9 |
5 3 10 | 397 |
【评测用例规模与约定】
30% | |
60% | |
80% | |
100% |
【解析及代码】
这个 L 都干到 了,如果用正常的 dp 思路,需要用
这么大的矩阵
先从简单的说起,就用这么大的矩阵做 dp,dp[i] 存储跳跃总长度为 i 时的方案数:
- dp[i][0] 表示下一次跳跃无限制的方案数(即上一次跳跃 < p):
- dp[i][1] 表示下一次跳跃有限制的方案数(即上一次跳跃 ≥ p):
可见,dp[i] 只与其之前的 k 个状态有关,那么只循环使用一个 k × 2 的矩阵:
- dp[i] 表与当前位置距离为 i + 1 的方案数
- 使本次跳跃无限制的方案数:本次跳跃 < p,dp[: p-1] 的和
- 使本次跳跃有限制的方案数:本次跳跃 ≥ p,dp[p-1: k, 0] 的和
from collections import deque
k, p, l = map(int, input().split())
mod = 20201114
res = 0
# p > 1 时答案不为 0, p = 1 时答案为 0
if p > 1:
# dp[i] 表与当前位置距离为 i + 1 的方案数: 跳跃无限制, 跳跃有限制
dp = deque([(1, 0)] + [(0, 0)] * k, maxlen=k + 1)
# 使本次无限制: dp[:p - 1] 的和 (跳跃长度 < p)
# 使本次有限制: dp[p - 1: k, 0] 的和 (跳跃长度 >= p)
sum_0, sum_1 = 0, 0
for _ in range(l):
sum_0 = (sum_0 + sum(dp[0]) - sum(dp[p - 1])) % mod
sum_1 = (sum_1 + dp[p - 1][0] - dp[k][0]) % mod
# 跳到当前位置的方案数, 在下一轮迭代作为与起点距离为 1 的方案数
dp.appendleft((sum_0, sum_1))
res = sum(dp[0]) % mod
print(res)
补给 73🏆
【题目描述】
小蓝是一个直升飞机驾驶员,他负责给山区的 n 个村庄运送物资。
每个月,他都要到每个村庄至少一次,可以多于一次,将村庄需要的物资运送过去。
每个村庄都正好有一个直升机场,每两个村庄之间的路程都正好是村庄之间的直线距离。
由于直升机的油箱大小有限,小蓝单次飞行的距离不能超过 D。每个直升机场都有加油站,可以给直升机加满油。
每个月,小蓝都是从总部出发,给各个村庄运送完物资后回到总部。如果方便,小蓝中途也可以经过总部来加油。
总部位于编号为 1 的村庄。
请问,要完成一个月的任务,小蓝至少要飞行多长距离?
【输入格式】
输入的第一行包含两个整数 n , D,分别表示村庄的数量和单次飞行的距离。
接下来 n行描述村庄的位置,其中第 i 行两个整数 xi , yi 分别表示编号为 i 的村庄的坐标。村庄 i 和村庄 j 之间的距离为
【输出格式】
输出一行,包含一个实数,四舍五入保留正好 2 位小数,表示答案
【样例】
输入 | 输出 |
4 6 1 1 4 5 8 5 11 1 | 28.00 |
【评测用例规模与约定】
100% |
【解析及代码】
这个题 n 的数据规模是 [1, 20],所以思路就是 图 + 状态压缩
在创建邻接矩阵的时候,保证飞机单次飞行的距离不会超限,然后用 Floyd 算法处理一下邻接矩阵,求出多源最短路
用二进制数来表示经过的地点。如 0000 表示停留在起点 (第 1 个地点) 未经过任何地点,0010 表示从起点出发经过第 2 个地点,1111 表示从起点出发经过第 2、3、4 个地点并返回起点。现在依据“经过几个地点”对二进制状态进行分层
bin 函数可以将整数转化为二进制字符串,如:5 -> "0b101"。利用该函数可得出经过地点的数目 n,然后利用位运算筛除所有包含第 1 个地点的状态。假设总共有 4 个地点,因为第 1 个地点是要最后到达的,所以最终状态应该是 1111 (15),即 (1 << 4) - 1,把这个状态分在最后一层
ls (二进制状态) 取自 dp[cnt - 1],cs 取自 dp[cnt],所以已经过地点的数目必满足:cs - ls == 1。假设 ls 是 0010,cs 是 0101,利用 ls | cs == cs 这个条件可以筛除掉这种不合法的状态转移
假设 ls 是 0010,cs 是 0110,cs - ls = 0100,用len(bin(cs - ls[2:])) - 1 可以求出新经过的地点索引
import itertools as it
import math
# 表示村庄的数量、飞行距离限制
n, limit = map(int, input().split())
# 记录每个顶点的坐标: 复数存储
loc = []
for _ in range(n):
x, y = map(int, input().split())
loc.append(x + y * 1j)
# 初始化邻接矩阵
adj = [[math.inf] * n for _ in range(n)]
for i in range(n):
for j in range(i + 1, n):
value = abs(loc[i] - loc[j])
# 如果距离允许则直接填入邻接矩阵
if value <= limit: adj[i][j] = adj[j][i] = value
# Floyd 算法求解多源最短路
for m in range(n):
for i, j in it.combinations(it.chain(range(m), range(m + 1, n)), 2):
adj[i][j] = adj[j][i] = min(adj[i][j], adj[i][m] + adj[m][j])
# dp[i][j][k]
# i: 从起点出发并经过 i 个村庄
# j: 二进制状态
# k: 当前停留在 k 号村庄
dp = [{} for _ in range(n + 1)]
for s in range(1 << n):
# 筛选掉所有经过起点的状态
if not s & 1:
cnt = bin(s).count('1')
dp[cnt][s] = {}
# 终态: 已经过所有村庄
tar = (1 << n) - 1
dp[-1][tar] = {}
# 初态: 停留在起点 (0 号村庄)
dp[0][0] = {0: 0}
for cnt in range(1, n + 1):
for ls in dp[cnt - 1]:
for cs in dp[cnt]:
# 判断状态转移是否合法
if ls | cs == cs:
# 当前状态的停留地
cstay = len(bin(cs - ls)[2:]) - 1
# 枚举上一个状态的所有停留地
for lstay in dp[cnt - 1][ls]:
v = dp[cnt - 1][ls][lstay] + adj[lstay][cstay]
if math.isfinite(v):
dp[cnt][cs][cstay] = min(v, dp[cnt][cs].get(cstay, math.inf))
print(f'{dp[-1][tar][0]:.2f}')