第十四届蓝桥杯Python B组省赛复盘
文章目录
- 第十四届蓝桥杯Python B组省赛复盘
- 试题 A: 2023
- 【问题描述】(5 分)
- 【思路】
- 试题 B: 硬币兑换
- 【问题描述】
- 【思路】
- 试题 C: 松散子序列
- 【问题描述】
- 【输入格式】
- 【输出格式】
- 【样例输入】
- 【样例输出】
- 【评测用例规模与约定】
- 思路
- 试题 D: 管道
- 【问题描述】
- 【输入格式】
- 【输出格式】
- 【样例输入】
- 【样例输出】
- 【评测用例规模与约定】
- 思路
- 试题 E: 保险箱
- 【问题描述】
- 【输入格式】
- 【输出格式】
- 【样例输入】
- 【样例输出】
- 【评测用例规模与约定】
- 思路
试题 A: 2023
【问题描述】(5 分)
请求出在 12345678 至 98765432 中,有多少个数中完全不包含 2023 。
完全不包含 2023 是指无论将这个数的哪些数位移除都不能得到 2023 。
例如 20322175,33220022 都完全不包含 2023,而 20230415,20193213 则 含有 2023 (后者取第 1, 2, 6, 8 个数位) 。
【思路】
-
正则表达式
''' 正则表达式 ''' import re def check(s) : if re.match(r'.*2.*0.*2.*3.*', s) : return False else : return True st = 12345678 ed = 98765432 res = 0 while st <= ed : if check(str(st)) : res += 1 st += 1 print(res)
-
信号量机制(哨兵)
从后往前,s1,s2,s3,s4分别标记3,2,0,2是否已经找到。大致思路是只有前面的数全找到后才能找下一个数。比如当找0时,必须确保从后往前已经找到了3,2这个两个数。def check(n) : s1, s2, s3, s4 = False, False, False, False for i in range(8) : if n % 10 == 3 and not s1 : s1 = True elif n % 10 == 2 and s1 and not s2 : s2 = True elif n % 10 == 0 and s1 and s2 and not s3: s3 = True elif n % 10 == 2 and s1 and s2 and s3 and not s4: s4 =True n //= 10 return not (s1 and s2 and s3 and s4) st = 12345678 ed = 98765432 res = 0 while st <= ed : if check(str(st)) : res += 1 st += 1 print(res)
试题 B: 硬币兑换
【问题描述】
小蓝手中有 2023 种不同面值的硬币,这些硬币全部是新版硬币,其中第
i
(
1
≤
i
≤
2023
)
i(1 ≤ i ≤ 2023)
i(1≤i≤2023) 种硬币的面值为
i
i
i ,数量也为
i
i
i 个。硬币兑换机可以进行硬币兑换,兑换规则为:交给硬币兑换机两个新版硬币
c
o
i
n
1
coin_1
coin1 和
c
o
i
n
2
coin_2
coin2 ,硬币兑换机会兑换成一个面值为
c
o
i
n
1
+
c
o
i
n
2
coin_1 + coin_2
coin1+coin2 的旧版硬币。小蓝可以用自己已有的硬币进行任意次数兑换,假设最终小蓝手中有
K
K
K 种不同面值的硬币(只看面值,不看新旧)并且第
i
(
1
≤
i
≤
K
)
i(1 ≤ i ≤ K)
i(1≤i≤K) 种硬币的个数为
s
u
m
i
sum_i
sumi。小蓝想要使得
m
a
x
{
s
u
m
1
,
s
u
m
2
,
⋅
⋅
⋅
,
s
u
m
K
}
max\{sum_1, sum_2, · · · , sum_K\}
max{sum1,sum2,⋅⋅⋅,sumK} 的值达到最大,请你帮他计算这个值最大是多少。
注意硬币兑换机只接受新版硬币进行兑换,并且兑换出的硬币全部是旧版硬币。
【思路】
我们知道硬币的数量是随着面值单调增加,那么两个面值要拼凑出一个大的面值,取决于小的面值。
假设有n中面值硬币,
a
n
−
1
,
a
n
a_{n - 1}, a_n
an−1,an分别表示尽可能拼凑出最多个面额分别为
n
−
1
,
n
n - 1,n
n−1,n的硬币。
a
n
−
1
=
n
−
1
+
1
+
.
.
.
+
i
n
t
(
n
−
1
/
2
)
a_{n - 1} = n - 1 + 1+ ...+int(n - 1 / 2)
an−1=n−1+1+...+int(n−1/2)
a
n
=
n
+
1
+
.
.
.
+
n
/
2
a_n = n + 1 + ... + n/2
an=n+1+...+n/2
显然
a
n
a_n
an更大。所以最终拼凑出的数是大于等于
a
n
a_n
an
res = 0
for t in range(2023, 4047) :
ans = 0
for i in range(1, 2024) :
ta = t - i
if i == ta :
ans += (i // 2)
if ta <= i or ta > 2023 :
continue
ans += i
if t == 2023 :
ans += 2023
res = max(res, ans)
print(res)
'''
682425
'''
试题 C: 松散子序列
时间限制: 10.0s 内存限制: 512.0MB 本题总分:10 分
【问题描述】
给定一个仅含小写字母的字符串 s ,假设 s 的一个子序列 t 的第 i 个字符
对应了原字符串中的第 pi 个字符。我们定义 s 的一个松散子序列为:对于 i > 1
总是有
p
i
−
p
i
−
1
≥
2
p_i − p_{i−1} ≥ 2
pi−pi−1≥2 。设一个子序列的价值为其包含的每个字符的价值之和 (
a ∼ z 分别为 1 ∼ 26 ) 。
求 s 的松散子序列中的最大价值。
【输入格式】
输入一行包含一个字符串 s 。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
azaazaz
【样例输出】
78
【评测用例规模与约定】
思路
本题大概意旨是,不能选连续两个字符组成的序列,典型的打家劫舍板子
-
状态机
- 状态表示
f
[
i
,
0
/
1
]
f[i, 0/1]
f[i,0/1]:
- 集合:表示0~i的字符串(不0)选取1第i个字符的松散序列价值的集合
- 属性:max
- 状态计算: f [ i , 0 ] = m a x ( f [ i − 1 , 0 ] , f [ i − 1 , 1 ] ) f[i, 0] = max(f[i - 1, 0], f[i - 1, 1]) f[i,0]=max(f[i−1,0],f[i−1,1]), f [ i , 1 ] = f [ i − 1 , 0 ] + s [ i ] f[i, 1] =f[i - 1, 0] + s[i] f[i,1]=f[i−1,0]+s[i]
s = list(input()) n = len(s) for i in range(n) : s[i] = ord(s[i]) - ord('a') + 1 f = [[0, 0] for _ in range(n + 1)] for i in range(1, n + 1) : f[i][0] = max(f[i - 1][0], f[i - 1][1]) f[i][1] = f[i - 1][0] + s[i - 1] print(max(f[n][0], f[n][1]))
- 状态表示
f
[
i
,
0
/
1
]
f[i, 0/1]
f[i,0/1]:
-
线性DP
- 状态表示
f
[
i
]
f[i]
f[i]:
- 集合:表示以i字符结尾的松散序列价值的集合
- 属性:max
- 状态计算: f [ i ] = m a x ( f [ j ] ) + o r d ( s [ i ] ) , j < = i − 2 f[i] = max(f[j]) + ord(s[i]),j <=i-2 f[i]=max(f[j])+ord(s[i]),j<=i−2
''' 状态表示:f[i] 集合:表示以i结尾的满足条件的子序列的价值集合 属性:max 状态计算:f[i] = max(f[j]) + ord(s[i]),j <=i-2 ''' s = list(input()) n = len(s) f = [0] * (n + 2) maxx = 0 # 记录到i-2的最大值 for i in range(1, n + 1) : f[i] = maxx + ord(s[i - 1]) - ord('a') + 1 maxx = max(f[i - 1], maxx) print(max(maxx, f[n]))
- 状态表示
f
[
i
]
f[i]
f[i]:
试题 D: 管道
时间限制: 10.0s 内存限制: 512.0MB 本题总分:10 分
【问题描述】
有一根长度为 len 的横向的管道,该管道按照单位长度分为 len 段,每一段
的中央有一个可开关的阀门和一个检测水流的传感器。
一开始管道是空的,位于
L
i
L_i
Li 的阀门会在
S
i
S_i
Si 时刻打开,并不断让水流入管
道。
对于位于
L
i
L_i
Li 的阀门,它流入的水在
T
i
(
T
i
≥
S
i
)
T_i (T_i ≥ S_i)
Ti(Ti≥Si) 时刻会使得从第
L
i
−
(
T
i
−
S
i
)
L_i−(T_i−S_i)
Li−(Ti−Si)
段到第
L
i
+
(
T
i
−
S
i
)
L_i + (T_i − S_i)
Li+(Ti−Si) 段的传感器检测到水流。
求管道中每一段中间的传感器都检测到有水流的最早时间。
【输入格式】
输入的第一行包含两个整数 n, len,用一个空格分隔,分别表示会打开的阀
门数和管道长度。
接下来 n 行每行包含两个整数
L
i
,
S
i
L_i, S_i
Li,Si,用一个空格分隔,表示位于第
L
i
L_i
Li 段
管道中央的阀门会在
S
i
S_i
Si 时刻打开。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
3 10
1 1
6 5
10 2
【样例输出】
5
【评测用例规模与约定】
思路
要求检测到水流的最短时间,看到len的数据范围是
1
0
9
10^9
109应该用一个低于
O
(
n
)
O(n)
O(n)的算法。
这就让我们想到了二分。通过二分右半段,可以查找满足全部覆盖的最短时间。
对于确定的时刻
t
t
t,一个在
s
s
s时刻
(
t
>
=
s
)
(t >= s)
(t>=s)打开并位于
l
l
l的管道,在此时此刻可以覆盖的区间是
[
m
a
x
(
l
−
(
t
−
s
)
,
1
)
,
m
i
n
(
l
+
(
t
−
s
)
,
l
e
n
)
]
[max(l - (t - s), 1), min(l + (t - s), len)]
[max(l−(t−s),1),min(l+(t−s),len)].
下面考虑区间覆盖问题,对于n个区间,可以一个个枚举区间,并对覆盖区间进行标记,然而这样的复杂度是
O
(
n
l
e
n
)
O(nlen)
O(nlen)。怎么优化呢?我们想到对区间进行操作的一个基础算法差分,类似于LeetCode中的区间覆盖原题。
'''
覆盖问题,二分
'''
from sys import stdin
def check(x) :
st = [0] * (m + 2)
for item in a :
if item[1] <= x :
l, r = max(item[0] - (x - item[1]), 1), min(item[0] + (x - item[1]), m)
st[l] += 1
st[r + 1] -= 1
for i in range(1, m + 1) :
st[i] += st[i - 1]
if st[i] <= 0 :
return False
return True
n, m = map(int, input().split())
a = []
for _ in range(n) :
l, s = map(int, stdin.readline().split())
a.append([l, s])
l, r = 0, m + 1
while l < r :
mid = (l + r) >> 1
if check(mid) :
r = mid
else :
l = mid + 1
print(l)
试题 E: 保险箱
时间限制: 10.0s 内存限制: 512.0MB 本题总分:15 分
【问题描述】
小蓝有一个保险箱,保险箱上共有 n 位数字。
小蓝可以任意调整保险箱上的每个数字,每一次操作可以将其中一位增加
1 或减少 1 。
当某位原本为 9 或 0 时可能会向前(左边)进位/退位,当最高位(左边第
一位)上的数字变化时向前的进位或退位忽略。
例如:
00000 的第 5 位减 1 变为 99999 ;
99999 的第 5 位减 1 变为 99998 ;
00000 的第 4 位减 1 变为 99990 ;
97993 的第 4 位加 1 变为 98003 ;
99909 的第 3 位加 1 变为 00009 。
保险箱上一开始有一个数字 x,小蓝希望把它变成 y,这样才能打开它,问
小蓝最少需要操作的次数。
【输入格式】
输入的第一行包含一个整数 n 。
第二行包含一个 n 位整数 x 。
第三行包含一个 n 位整数 y 。
【输出格式】
输出一行包含一个整数表示答案。
【样例输入】
5
12349
54321
【样例输出】
11
【评测用例规模与约定】
思路
比赛的时候看到这题秒想用BFS,但本题的数据范围实在太大,即使跑10s超过10长度的数据也跑不出来。这时我问了ChatGPT,它如是说