详细解析第十五届蓝桥杯 Python B 组省赛八道试题。涉及字符串枚举、容斥原理、哈希表、时间处理、分类讨论、动态规划、图论缩点及贪心算法等知识点。提供各题解题思路、Python 代码实现及测试样例,帮助读者掌握竞赛解题技巧。
佛系玩家23 浏览
A:穿越时空之门
考察:字符串、枚举
解题思路
从 1 枚举到 2024,遇到符合条件的数就计数器加一,最后输出计数器即可。
代码
ans = 0defcheck(x):
# 判断是否符合条件
s1, s2 = sum(int(i) for i inbin(x)[2:]), 0while x:
s2 += x % 4
x //= 4return s1 == s2
for i inrange(1, 2025):
ans += check(i)
print(ans) # 63
如果使用枚举的方式来统计的话,时间复杂度是 O(n^3),会超时,所以这里可以用一个哈希表来记录从左上到右下,或者从左下到右上方向的同一斜线方向上某个值出现的次数。然后由于同一斜向上每两个值相同,但位置不同的元素都能组成一对,所以用 c[x] 代表这一方向上 x 出现的个数,C(c[x])^2 就是格子对数。
然后再枚举不同斜线方向上的 c 并累加就行。
这里用一个函数来计算左上到右下方向上的,从左下到右上方向上的再把数组每一行逆序一下再计算就行了。
代码
from collections import *
n, m = map(int, input().split())
a = [list(map(int, input().split())) for _ inrange(n)]
defacc(a):
# 统计从左上到右下方向上的格子对数量
res = 0for k inrange(-(n - 1), m):
i, j = -k * (k < 0), k * (k > 0)
# 该方向上第一个位置
c = Counter()
while i < n and j < m:
c[a[i][j]] += 1
i += 1
j += 1for v in c.values():
res += v * (v - 1)
return res
print(acc(a) + acc([a[i][::-1] for i inrange(n)]))
注意要使用更新前的 f1 来更新 f2 和用更新前的 f2 更新 f1,所以需要对其中一个哈希表拷贝一下。
最后输出两个哈希表中的最大值即可。
代码
cin = lambda: list(map(int, input().split()))
input()
f1, f2 = [-1] * 5, [-1] * 5
digs = (0, 2, 4)
defupdate(s, f1, f2):
for d in digs:
ifstr(d) in s:
f1[d] = max(f1[d], max(f2[i] + 1for i in digs ifstr(i) in s))
for s, t inzip(cin(), cin()):
s, t = str(s), str(t)
f2_ = f2.copy() # 拷贝
update(t, f2, f1)
for d in digs:
ifstr(d) in s and f1[d] < 0:
f1[d] = max(*f1, 1) # 确保先从 s 开始走
update(s, f1, f2_)
print(max(f1 + f2))
测试样例
样例一
输入:
5
126 393 581 42 44
204 990 240 46 52
输出:
4
样例二
输入:
5
10 42 44 42 22
24 40 52 42 44
输出:
5
G:缴纳过路费
考察:缩点、并查集、DFS
解题思路
分析:由于要满足路径中最贵的一次收费在 [L, R] 区间内,所以收费大于 R 的路径是一定不可以经过的,而对于收费小于 L 的路径可以经过,但是不能让整条路径的收费全部小于 L,要不然路径的最大值也小于 L 了。
对此,我们要:将所有小于 L 的边连接的点缩成一个点(缩点),并记录缩后的点数(就是记录有多少个点缩成了这一个点),大于 R 的边则直接舍弃(不加到图中)。
然后再搜索满足条件的点对的数量,比如:有 3 个点缩成的点 A 和 2 个点缩成的点 B 之间有一条 [L, R] 之间的边,那么就有 2 x 3 = 6 个点对。示意图如下:
怎么实现缩点呢?并查集!将所有小于 L 的边连接的点用并查集来合并,让集合的根作为合并后的新点。注意记录集合的大小(也就是新点是由多少旧点合并来的)。
最后使用 DFS 累加点对数量(也可以用再用一个并查集找联通块)。比如点 A 是由 3 个旧点合并而来,与其连通的有 2 个新点,对应 5 个旧点,那么以 A 为第一个坐标的点对就有 3 x (8 - 3) 对。
注意累加的点对有重复((1, 2)、(2, 1) 是同一对),最后要除以二。
代码
from sys import *
setrecursionlimit(10**6)
cin = lambda: list(map(int, input().split()))
n, m, l, r = cin()
e = [set() for _ inrange(n + 1)] # s[i] == i 表示 i 为新点,sz[i] 表示新点 i 的大小
s, sz = list(range(n + 1)), [1] * (n + 1)
deffind(x):
if s[x] == x:
return x
s[x] = find(s[x])
return s[x]
edges = [cin() for _ inrange(m)]
for u, v, w in edges:
if w > r:
continue# 舍弃
su, sv = find(u), find(v)
if w < l and su != sv: # 边权小于 l 且不在同一集合# 合并
s[su] = sv
sz[sv] += sz[su] # 以新点建图for u, v, w in edges:
if l <= w <= r:
su, sv = find(u), find(v)
e[su].add(sv)
e[sv].add(su)
vis = [False] * (n + 1)
defdfs(u, block):
# 寻找连通块,返回值表示该块内点的集合的总大小
block.append(u)
res, vis[u] = sz[u], Truefor v in e[u]:
if vis[v]:
continue
res += dfs(v, block)
return res
ans = 0for i inrange(1, n + 1):
if s[i] == i andnot vis[i]:
block = []
t = dfs(i, block)
for u in block:
ans += sz[u] * (t - sz[u])
print(ans // 2)
from collections import *
cin = lambda: list(map(int, input().split()))
for _ inrange(int(input())):
n, k = cin()
cnt = Counter()
for i inrange(n):
a, b = cin()
cnt[a] += b
ifsum(c // 3for c in cnt.values()) < k:
print(-1)
continue
ans, v = 0, list(cnt.values())
for i inrange(len(v)):
if v[i] < 3:
ans += v[i]
v[i] = 0continue
t = min((v[i] - 2) // 3, k)
v[i] -= t * 3 + 2
ans += t * 3 + 2
k -= t
ifnot k:
print(ans - 2)
continue
v.sort()
print(ans + sum(v[-k:]) - (v[-k] == 2))