测试链接:https://www.dotcpp.com/oj/train/1022/
砝码称重 100🏆
【题目描述】
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 。
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边。
【输入格式】
输入的第一行包含一个整数 N。
第二行包含 N 个整数:。
【输出格式】
输出一个整数代表答案
【样例】
输入 | 输出 | 说明 |
3 1 4 6 | 10 | 能称出的 10 种重量是: 1 = 1 2 = 6 - 4 (天平一边放 6,另一边放 4) 3 = 4 - 1 4 = 4 5 = 6 - 1 6 = 6 7 = 1 + 6 9 = 4 + 6 - 1 10 = 4 + 6 11 = 1 + 4 + 6 |
【评测用例规模与约定】
50% | 1 ≤ N ≤ 15 |
100% | 1 ≤ N ≤ 100,N 个砝码总重不超过 100000 |
【解析及代码】
典型的背包问题,用一个一维列表 last 来记录状态,用布尔类型来表示可行性。如:last[1] 表示是否可叠加到重量 1,last[4] 表示是否可叠到重量 4
比如输入的砝码是 [1, 4, 6],我们是先考虑 [1],再考虑 [1, 4],再考虑 [1, 4, 6],即一个一个地把砝码加入考虑范围
做状态转移时,先把上一层的状态拷贝给本层,即 new = last.copy(),然后再根据 last 在 new 上做修改 (如果不这么做会出现一个砝码被多次使用的情况,可以自己推一下)
在 last[sum] == True 的情况下,砝码叠加有两种情况 (sum 为枚举的砝码总重量,w 为新考虑的砝码重量):
- 正向叠加: 加一个砝码得 new[sum + w] = True
- 反向叠加:再分两种情况
- 若 w = 4,sum = 1,可以像 1 → 0 → 3 这样叠加,new[w - sum] = True
- 若 w = 1,sum = 4,可以像 4 → 3 这样叠加,new[sum - w] = True
n = int(input())
weight = list(map(int, input().split()))
total = sum(weight)
last = [True] + [False] * total
for w in weight:
# 继承上一层的状态
new = last.copy()
for wsum in filter(last.__getitem__, range(total + 1)):
# 正向叠加
tmp = wsum + w
if tmp <= total: new[tmp] = True
# 反向叠加
new[abs(w - wsum)] = True
last = new
print(sum(last[1:]))
异或数列 83🏆
【题目描述】
Alice 和 Bob 正在玩一个异或数列的游戏。初始时,Alice 和 Bob 分别有一个整数 a 和 b,初始值均为 0。有一个给定的长度为 n 的公共数列 。
Alice 和 Bob 轮流操作,Alice 先手,每步可以在以下两种选项中选一种:
选项 1:从数列中选一个 给 Alice 的数异或上,或者说令 a 变为
(其中
表示按位异或)
选项 2:从数列中选一个 给 Bob 的数异或上,或者说令 b 变为
每个数 都只能用一次,当所有
均被使用后(n 轮后)游戏结束。游戏结束时,拥有的数比较大的一方获胜,如果双方数值相同,即为平手。
现在双方都足够聪明,都采用最优策略,请问谁能获胜?
【输入格式】
每个评测用例包含多组询问。询问之间彼此独立。
输入的第一行包含一个整数 T,表示询问数。
接下来 T 行每行包含一组询问。其中第 i 行的第一个整数 表示数列长度,随后
个整数
表示数列中的每个数。
【输出格式】
输出 T 行,依次对应每组询问的答案。
每行包含一个整数 1、0 或 -1 分别表示 Alice 胜、平局或败。
【样例】
输入 | 输出 |
4 1 1 1 0 2 2 1 7 992438 1006399 781139 985280 4729 872779 563580 | 1 0 1 1 |
【评测用例规模与约定】
100% | ![]() |
【解析及代码】
异或的规则是相同为 0,相异为 1。所以在初始值为 0 的情况下,异或奇数次 1 时为 1,异或偶数次 1 时为 0,而与异或 0 的次数无关
假设现给定 m 个 1 和 (n-m) 个 0,记 Alice 的数异或 1 的次数为 x,Bob 的为 y:
- 当 m 为 1 时,Alice 先手抢 1 给自己异或上,自己就稳赢了
- 当 m 为偶数时,(x, y) 可能为 (奇, 奇),(偶, 偶),也就是平局
- 当 m 为奇数时,谁先取倒数第二个 1 就稳输 :
- (x, y) 为 (奇, 偶) 时,如果 Alice 先取 1 把局势变为 (奇, 奇) / (偶, 偶),这时 Bob 只要取最后的 1 就可以变为 (偶, 奇) 取胜。此时换作 Bob 先取 1 也同理
- (x, y) 为 (偶, 奇) 时,与上述分析同理
m 为奇数且剩下两个 1 时,谁都不会主动取 1,只能先把 0 取完。n 为奇数时 Bob 会先取倒数第二个 1,Alice 胜;n 为偶数时 Alice 被迫先取,Bob 胜
然后把结论推广,把一组整数全化作二进制数,先看最高位的 1 的个数,如果是奇数就一定能分胜负,否则看次高位,以此类推
def judge(bins, length):
for one in reversed(bins):
# Alice 先手直接赢
if one == 1: return 1
# 当最高位为奇数, 先取倒数第二个 1 的输
if one & 1: return 1 if length & 1 else -1
return 0
for _ in range(int(input())):
length, *nums = map(int, input().split())
# 统计各个数位上 1 的个数
bins = [0] * 21
for n in nums:
i = 0
while n:
bins[i] += n & 1
n, i = n >> 1, i + 1
# 进行胜负的判断
print(judge(bins, length))
没有满分的原因是运行时出错了,我感觉是测评数据的格式问题 …… 点到为止
左孩子右兄弟 82🏆
【题目描述】
对于一棵多叉树,我们可以通过 “左孩子右兄弟” 表示法,将其转化成一棵二叉树。
如果我们认为每个结点的子结点是无序的,那么得到的二叉树可能不唯一。换句话说,每个结点可以选任意子结点作为左孩子,并按任意顺序连接右兄弟。
给定一棵包含 N 个结点的多叉树,结点从 1 至 N 编号,其中 1 号结点是根,每个结点的父结点的编号比自己的编号小。请你计算其通过 “左孩子右兄弟” 表示法转化成的二叉树,高度最高是多少。注:只有根结点这一个结点的树高度为 0 。
例如如下的多叉树:
可能有以下 3 种 (这里只列出 3 种,并不是全部) 不同的 “左孩子右兄弟”表示:
其中最后一种高度最高,为 4
【输入格式】
输入的第一行包含一个整数 N。
以下 N 行,每行包含一个整数,依次表示 2 至 N 号结点的父结点编号。
【输出格式】
输出一个整数代表答案
【样例】
输入 | 输出 |
5 1 1 1 2 | 4 |
【评测用例规模与约定】
30% | 1 ≤ N ≤ 20 |
100% | 1 ≤ N ≤ 100000 |
【解析及代码】
对某一个结点 (可以是根结点) 而言,其最大深度 = 子结点的个数 + 子结点的最大深度的最大值。该结点的最大深度是否能为父结点作贡献,又要和它的兄弟结点进行比较
所以编写一个继承列表的类 Node 表示结点,编写递归函数 max_depth 求每一个结点的最大深度
Node 用于存放其子结点的索引 (如 1, 2, ..., n),直接用 append 函数添加即可,这些信息可以提供给 max_depth 函数调用
class Node(list):
tree = None
def max_depth(self):
# 子结点的子结点数
grandsons = [self.tree[i].max_depth() for i in self]
return len(self) + max([0] + grandsons)
# 初始化 N 个结点, 0 号结点为根结点
tree = [Node() for _ in range(int(input()))]
Node.tree = tree
# 为 0 号结点以外的结点寻找父结点
for i in range(1, len(tree)):
tree[int(input()) - 1].append(i)
print(tree[0].max_depth())
这个没满分也是运行时错误,最大的可能就是递归超过最大深度了 (毕竟结点数限制 10w 个)
可以写个栈消除递归,但是更多的代码意味着出错的可能性更高,不推荐
括号序列
【题目描述】
给定一个括号序列,要求尽可能少地添加若干括号使得括号序列变得合法,当添加完成后,会产生不同的添加结果,请问有多少种本质不同的添加结果。两个结果是本质不同的是指存在某个位置一个结果是左括号,而另一个是右括号。
例如,对于括号序列 (((),只需要添加两个括号就能让其合法,有以下几种不同的添加结果:()()()、()(())、(())()、(()()) 和 ((()))。
【输入格式】
输入一行包含一个字符串 s,表示给定的括号序列,序列中只有左括号和右括号。
【输出格式】
输出一个整数表示答案,答案可能很大,请输出答案除以 的余数。
【样例】
输入 | 输出 |
((() | 5 |
【评测用例规模与约定】
40% | ![]() |
100% | ![]() |
【解析及代码】
挂个大佬的题解,有空再研究:12届蓝桥杯省赛c++b组 J题 括号序列
分果果
【题目描述】
小蓝要在自己的生日宴会上将 n 包糖果分给 m 个小朋友。每包糖果都要分出去,每个小朋友至少要分一包,也可以分多包。
小蓝已经提前将糖果准备好了,为了在宴会当天能把糖果分得更平均一些,小蓝要先计算好分配方案。
小蓝将糖果从 1 到 n 编号,第 i 包糖果重 。小朋友从 1 到 m 编号。每个小朋友只能分到编号连续的糖果。小蓝想了很久没想出合适的分配方案使得每个小朋友分到的糖果差不多重。因此需要你帮他一起想办法。为了更好的分配糖果,他可以再买一些糖果,让某一些编号的糖果有两份。当某个编号的糖果有两份时,一个小朋友最多只能分其中的一份。
请找一个方案,使得小朋友分到的糖果的最大重量和最小重量的差最小,请输出这个差。
例如,小蓝现在有 5 包糖果,重量分别为 6, 1, 2, 7, 9,如果小蓝要分给两个小朋友,则他可以将所有糖果再买一份,两个小朋友都分到 1 至 5 包糖果,重量都是 25,差为 0。
再如,小蓝现在有 5 包糖果,重量分别为 6, 1, 2, 7, 9,如果小蓝要分给三个小朋友,则他可以将第 3 包糖果再买一份,第一个小朋友分 1 至 3 包,第二个小朋友分 3 至 4 包,第三个小朋友分第 5 包,每个小朋友分到的重量都是 9,差为 0。
再如,小蓝现在有 5 包糖果,重量分别为 6, 1, 2, 7, 9,如果小蓝要分给四个小朋友,则他可以将第 3 包和第 5 包糖果再买一份,仍然可以每个小朋友分到的重量都是 9,差为 0。
再如,小蓝现在有 5 包糖果,重量分别为 6, 1, 2, 7, 9,如果小蓝要分给五个小朋友,则他可以将第 4 包和第 5 包糖果再买一份,第一个小朋友分第 1 至 2 包重量为 7,第二个小朋友分第 3 至 4 包重量为 9,第三个小朋友分第 4 包重量为 7,第四个和第五个小朋友都分第 5 包重量为 9。差为 2。
【输入格式】
输入第一行包含两个整数 n 和 m,分别表示糖果包数和小朋友数量。
第二行包含 n 个整数 ,表示每包糖果的重量。
【输出格式】
输出一个整数,表示在最优情况下小朋友分到的糖果的最大重量和最小重量的差。
【样例】
输入 | 输出 |
5 2 6 1 2 7 9 | 0 |
【评测用例规模与约定】
30% | ![]() |
60% | ![]() |
100% | ![]() |
【解析及代码】
时间显示 100🏆
【题目描述】
小蓝要和朋友合作开发一个时间显示的网站。在服务器上,朋友已经获取了当前的时间,用一个整数表示,值为从 1970 年 1 月 1 日 00:00:00 到当前时刻经过的毫秒数。现在,小蓝要在客户端显示出这个时间。小蓝不用显示出年月日,只需要显示出时分秒即可,毫秒也不用显示,直接舍去即可。
给定一个用整数表示的时间,请将这个时间对应的时分秒输出。
【输入格式】
输入一行包含一个整数,表示时间。
【输出格式】
输出时分秒表示的当前时间,格式形如 HH:MM:SS,其中 HH 表示时,值为 0 到 23,MM 表示分,值为 0 到 59,SS 表示秒,值为 0 到 59。时、分、秒不足两位时补前导 0。
【样例】
输入 | 输出 |
46800999 | 13:00:00 |
【评测用例规模与约定】
100% | 给定的时间为不超过 ![]() |
【解析及代码】
这个题的难度肯定不是 Python 组的
import time
print(time.strftime("%H:%M:%S", time.gmtime(int(input()) // 1000)))
杨辉三角形 100🏆
【题目描述】
下面的图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...
给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?
【输入格式】
输入一个整数 N
【输出格式】
输出一个整数代表答案
【样例】
输入 | 输出 |
6 | 13 |
【评测用例规模与约定】
20% | ![]() |
100% | ![]() |
【解析及代码】
可以参考另一位大佬的思路 Link,利用数据规模规定搜索边界,用二分法进行查找的
记行索引为 n 列索引为 k 的数的位置为 (n, k),杨辉三角形有以下几个性质:
- 位置为 (n, k) 的数
,由其上的两个数字相加得到
- 索引为 n 的行中的最大数为
,在 n 为偶数时,该行的数关于
对称。所以我们只需要考虑杨辉三角形的左半边,每一行都是一个单调增的数列
不妨定义一条搜索路径,以搜索 16 为例:
- 阶段 1 (蓝色线):从 n = 2 开始出发,计算每一行最大数的
,并与 16 比较。
则 16 肯定不在索引 n 的行;
则直接退出;
则说明 16 可能在这一行 (n = 6),进入下一步
- 阶段 2 (橙色线):在 n = 6 开始,如果
, 则向左搜索 (即 k - 1);如果
则直接退出;如果
,此时便找到了 16 在该行的排序位置 (即
),考虑下一行的数 (即 n + 1):
- 右下方的数
一定大于右方的数
,已知
,则
,不用考虑
- 左下方的数
与 16 的大小关系未知,向其移动与其比较,并重复阶段 2
此外,为了避免组合数的阶乘带来沉重的计算量,定义以下转换 (写代码时先乘后除,避免出小数):
- 向左下移动 (n + 1):
- 向左移动 (k - 1):
- 向右移动 (k + 1):
因为阶段 2 的搜索没有向右移动,所以在 k = 1 时可以断定 16 一定在 (16, 1) 的位置
再根据等差数列计算出 N = 16 的一维位置为:
def search(target):
if target == 1: return 1
# C(n, k) = n! / (n-k)! k!
n, k, comb = 2, 1, 2
# 阶段 1 的搜索
while target > comb:
# 向左下角移动一格
n += 1
comb = comb * n // (n - k)
# 向右靠一格 (n 为偶数时)
if (n + 1) & 1:
comb = comb * (n - k) // (k + 1)
k += 1
# 阶段 2 的搜索
while target != comb:
# 向左移一格
while target < comb:
comb = comb * k // (n - k + 1)
k -= 1
# k = 1, 无法再左移, 退出
if k == 1:
n = target
break
# 向左下移一格
if target != comb:
n += 1
comb = comb * n // (n - k)
return (1 + n) * n // 2 + k + 1
print(search(int(input())))
双向排序 64🏆
【题目描述】
给定序列 ,即
。
小蓝将对这个序列进行 m 次操作,每次可能是将 降序排列,或者将
升序排列。
请求出操作完成后的序列。
【输入格式】
输入的第一行包含两个整数 n, m,分别表示序列的长度和操作次数。
接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 表示操作类型和参数。当
时,表示将
降序排列;当
时,表示将
升序排列。
【输出格式】
输出一行,包含 n 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
【样例】
输入 | 输出 |
3 3 0 3 1 2 0 2 | 3 1 2 |
【评测用例规模与约定】
30% | ![]() |
60% | ![]() |
100% | ![]() |
【解析及代码】
朴实无华的操作,也可以参考这个满分做法 (我有空再研究):https://blog.csdn.net/m0_64675532/article/details/129804672
n, m = map(int, input().split())
seq = list(range(1, n + 1))
for i in range(m):
is_ascending, q = map(int, input().split())
if is_ascending:
seq[q - 1:] = sorted(seq[q - 1:])
else:
seq[:q] = sorted(seq[:q], reverse=True)
print(*seq)
最少砝码 100🏆
【题目描述】
你有一架天平。现在你要设计一套砝码,使得利用这些砝码可以称出任意小于等于 N 的正整数重量。那么这套砝码最少需要包含多少个砝码?
注意砝码可以放在天平两边。
【输入格式】
输入包含一个正整数 N。
【输出格式】
输出一个整数代表答案。
【样例】
输入 | 输出 | 说明 |
7 | 3 | 3 个砝码重量是 1、4、6 4 = 4; 5 = 6 1; |
【评测用例规模与约定】
100% | ![]() |
【解析及代码】
选择 1 作为第一个砝码,此时可以测算的重量上限为 x = 1,可以提供 [-x, x] 的重量 (负数放左边,0 是不放,正数放右边):
- 第二个砝码选 3,和 [-1, 1] 叠加得到 [2, 4] 的重量,加上原本的测算上限是 1,则测算上限更新为 4 (1, 3-1, 3, 1+3)。现在可以提供 [-4, 4] 的重量
- 第三个砝码选 9,和 [-4, 4] 叠加得到 [5, 13] 的重量,加上原本的测算上限是 4,则测算上限更新为 13
所以 3 个砝码的测算上限为 13。根据 x 计算每一次选择的砝码为 2x+1,同时测算上限变为 3x+1,当测算上限大于等于 N 时,即可得到所需要的砝码数
n = int(input())
lim, t = 1, 1
while lim < n:
lim += 2 * lim + 1
t += 1
print(t)
异或变换 100🏆
【题目描述】
小蓝有一个 01 串 。
以后每个时刻,小蓝要对这个 01 串进行一次变换。每次变换的规则相同。
对于 01 串 ,变换后的 01 串
为:
其中 表示两个二进制的异或,当 a 和 b 相同时结果为 0,当 a 和 b不同时结果为 1。
请问,经过 t 次变换后的 01 串是什么?
【输入格式】
输入的第一行包含两个整数 n, t,分别表示 01 串的长度和变换的次数。
第二行包含一个长度为 n 的 01 串。
【输出格式】
输出一行包含一个 01 串,为变换后的串。
【样例】
输入 | 输出 | 说明 |
5 3 10110 | 11010 | 初始时为 10110, 变换 1 次后变为 11101, 变换 2 次后变为 10011, 变换 3 次后变为 11010。 |
【评测用例规模与约定】
40% | ![]() |
80% | ![]() |
100% | ![]() |
【解析及代码】
从数据规模来看,01 串的长度 n 不大,但是变换次数 t 大得惊人,应该是有循环
完成变换的最简单做法就是 ,但是需要验证
是否能保持不变:
的
,当
时,
,保持不变
- 当
时,
,保持不变
所以这个变换可以直接一行代码解决
位运算使用 int 类型来进行,用一个状态列表记录变换过程中不重复的 s,在找到循环的时候输出答案 (用 bin 转二进制时记得用 rjust 补 0)
n, t = map(int, input().split())
s = int(input(), 2)
# 查找循环
states = [s]
for _ in range(t):
s ^= s >> 1
states.append(s)
if s == states[0]: break
states.pop(0)
idx = -1 if len(states) == t else (t % len(states) - 1)
# 整数转二进制可能位数不够 n (s1 是 0), 记得补上
print(bin(states[idx])[2:].rjust(n, '0'))
冰山 64🏆
【题目描述】
一片海域上有一些冰山,第 i 座冰山的体积为 。
随着气温的变化,冰山的体积可能增大或缩小。第 i 天,每座冰山的变化量都是 。当
时,所有冰山体积增加
;当
时,所有冰山体积减少
;当
时,所有冰山体积不变。
如果第 i 天某座冰山的体积变化后小于等于 0,则冰山会永远消失。
冰山有大小限制 k。如果第 i 天某座冰山 j 的体积变化后 大于 k,则它会分裂成一个体积为 k 的冰山和
座体积为 1 的冰山。
第 i 天结束前 (冰山增大、缩小、消失、分裂完成后),会漂来一座体积为 的冰山 (
表示没有冰山漂来)。
小蓝在连续的 m 天对这片海域进行了观察,并准确记录了冰山的变化。小蓝想知道,每天结束时所有冰山的体积之和 (包括新漂来的) 是多少。
由于答案可能很大,请输出答案除以 998244353 的余数。
【输入格式】
输入的第一行包含三个整数 n, m, k,分别表示初始时冰山的数量、观察的天数以及冰山的大小限制。
第二行包含 n 个整数 ,表示初始时每座冰山的体积。
接下来 m 行描述观察的 m 天的冰山变化。其中第 i 行包含两个整数 ,意义如前所述。
【输出格式】
输出 m 行,每行包含一个整数,分别对应每天结束时所有冰山的体积之和除以 998244353 的余数。
【样例】
输入 | 输出 | 说明 |
1 3 6 1 6 1 2 2 -1 1 | 8 16 11 | 初始时的冰山为 [1]。 第 1 天结束时,有 3 座冰山:[1, 1, 6]。 第 2 天结束时,有 6 座冰山:[1, 1, 2, 3, 3, 6]。 第 3 天结束时,有 5 座冰山:[1, 1, 2, 2, 5]。 |
【评测用例规模与约定】
40% | ![]() |
60% | |
100% | |
【解析及代码】
看别的大佬都用 splay 树做的,学不来学不来
冰山的体积肯定不能用 list 存储,因为体积重复的情况下会引入很多重复的运算 (特别是冰山分裂),所以当然是用计数器 Counter
Counter 和 dict 类似,用键表示冰山体积,值表示该体积下的冰山数
Counter 的 update 在测评时很怪,所以重写了个类 IceBerg,用 extend 替代 update
为了进一步减少计算量,用全局变量 sum_ 计算初始的冰山总体积,实时更新冰山总数 n (消失、分裂、新来)。sum_ += x * n + y 可以初步算出新的总体积,在冰山消失时对其进行修正即可得到实际的总体积
from collections import Counter
class IceBerg(Counter):
def extend(self, key, cnt):
self[key] = self.get(key, 0) + cnt
mod = 998244353
n, m, k = map(int, input().split())
# 利用计数器记录体积, 冰山的体积之和
last = IceBerg(map(int, input().split()))
sum_ = sum(v * cnt for v, cnt in last.items()) % mod
for _ in range(m):
x, y = map(int, input().split())
# 计入新冰山, 新建计数器
sum_ += x * n + y
n += bool(y)
new = IceBerg([y] if y else [])
# 完成体积的变化
for v, cnt in last.items():
vn = v + x
# 冰山消失: 修正总体积, 更新冰山数
if vn <= 0:
n -= cnt
sum_ -= vn * cnt
# 冰山分裂: 更新冰山数
elif vn > k:
n += cnt * (vn - k)
new.extend(k, cnt)
new.extend(1, cnt * (vn - k))
# 冰山不分裂
else:
new.extend(vn, cnt)
# 覆盖原有计数器
last, sum_ = new, sum_ % mod
print(sum_)
翻转括号序列 43🏆
【题目描述】
给定一个长度为 n 的括号序列,要求支持两种操作:
1. 将 区间内 (序列中的第
个字符到第
个字符) 的括号全部翻转 (左括号变成右括号,右括号变成左括号)。
2. 求出以 为左端点时,最长的合法括号序列对应的
(即找出最大的
使
是一个合法括号序列)。
【输入格式】
输入的第一行包含两个整数 n, m,分别表示括号序列长度和操作次数。
第二行包含给定的括号序列,括号序列中只包含左括号和右括号。
接下来 m 行,每行描述一个操作。如果该行为 “”,表示第一种操作,区间为
;如果该行为 “
” 表示第二种操作,左端点为
。
【输出格式】
对于每个第二种操作,输出一行,表示对应的 。如果不存在这样的
,请输出 0。
【样例】
输入 | 输出 |
7 5 ((())() 2 3 2 2 1 3 5 2 3 2 1 | 4 7 0 0 |
【评测用例规模与约定】
20% | ![]() |
40% | ![]() |
60% | |
100% | ![]() |
【解析及代码】
用 1 表示左括号,0 表示右括号,把字符的替换 (for 循环或 3 × replace) 优化成 int 的运算
操作 1:在翻转前用 str 类型存储这个 01 串,在翻转时拆分字符串,并对中间的部分取反。例如对 [3, 5] 区间内的 100 取反,则是 111 - 100 = 11,用 bin 函数转回 01 字符串再存回去 (记得用 rjust 补 0)
操作 2:查找主要借助括号序列的一个性质,合法括号序列的左切片中的 左括号数量 ≥ 右括号数量
- 读取
后,先判断
位置是不是左括号,然后记录左括号溢出值为 lthan = 1
- 在向右查找的过程中,遇到左括号则使 lthan + 1,否则使 lthan - 1。当 lthan = 0,则当前位置可作为一个端点,记录;当 lthan < 0,则括号序列开始不合法,退出查找
- 而 lthan 可能出现很大的情况,例如搜索到位置 i 时,lthan > n - i,也就是剩下的字符就算全都是右括号,也不能使 lthan = 0,这种情况下就没必要继续查找了
n, m = map(int, input().split())
# 1 表左括号, 0 表右括号
stream = ''.join(map(lambda x: str(int(x == '(')), input()))
def find(l):
# 左括号数量 - 右括号数量
lthan, r = int(stream[l]), 0
# 起始需要是左括号
if lthan:
for i in range(l + 1, n):
# lthan 无法被剩余的右括号抵消时
# lthan 小于 0 时, 代表括号序列开始不合法
if lthan > n - i or lthan < 0: break
# 更新状态
lthan += 1 if stream[i] == '1' else -1
if lthan == 0: r = i + 1
return r
for _ in range(m):
mode, l, *r = map(int, input().split())
l -= 1
r = r[0] if r else None
# 翻转操作
if mode == 1:
part1, part2, part3 = stream[:l], stream[l:r], stream[r:]
# 对 [l, r] 区间内的数取反
part2 = bin(int('1' * (r - l), 2) - int(part2, 2))[2:]
# 转二进制时记得用 rjust 补 0 !
stream = part1 + part2.rjust(r - l, '0') + part3
# 查找操作
else:
print(find(l))
异或三角
【题目描述】
给定 T 个数 ,对每个
请求出有多少组
满足:
1. ;
2. ,其中
表示二进制按位异或;
3. 长度为 的三条边能组成一个三角形。
【输入格式】
输入的第一行包含一个整数 T。
接下来 T 行每行一个整数,分别表示 。
【输出格式】
输出 T 行,每行包含一个整数,表示对应的答案。
【样例】
输入 | 输出 |
2 6 114514 | 6 11223848130 |
【评测用例规模与约定】
10% | ![]() |
20% | |
50% | ![]() |
60% | ![]() |
100% | ![]() |
【解析及代码】
积木 33🏆
【题目描述】
小蓝有大量正方体的积木 (所有积木完全相同),他准备用积木搭一个巨大的图形。
小蓝将积木全部平铺在地面上,而不垒起来,以便更稳定。他将积木摆成一行一行的,每行的左边对齐,形成最终的图形。最终图形一共 n 行。
第一行小蓝摆了 块积木。从第二行开始,第 i 行的积木数量
都至少比上一行多 L,至多比上一行多 R (当 L = 0 时表示可以和上一行的积木数量相同),即
。
给定 x, y 和 z,请问满足以上条件的方案中,有多少种方案满足第 y 行的积木数量恰好为第 x 行的积木数量的 z 倍。
【输入格式】
输入一行包含 7 个整数 n, w, L, R, x, y, z,意义如上所述。
【输出格式】
输出一个整数, 表示满足条件的方案数,答案可能很大,请输出答案除以 998244353 的余数。
【样例】
输入 | 输出 | 说明 |
5 1 1 2 2 5 3 | 4 | ![]() |
233 5 1 8 100 215 3 | 308810105 |
【评测用例规模与约定】
10% | ![]() |
20% | ![]() |
35% | ![]() |
50% | ![]() |
60% | ![]() |
70% | ![]() |
85% | ![]() |
100% | |
【解析及代码】
对于第 1 行、第 x 行、第 y 行的积木数,合法积木数量 应该满足:
利用第一个不等式,可以用 for 循环枚举 ;而第二个不等式结合
又可以判断这个
能不能用
有了固定好的 之后,把问题分解成 3 个部分:
- 第 x 行之前积木放置的方案数:x-1 个体积为
的物品放进容积为
的背包的方案数
- 第 x ~ y 行的积木放置的方案数:y-x 个体积为
的物品放进容积为
的背包的方案数
- 第 y 行之后积木放置的方案数:随便放也就是
前两个都是背包问题,把物品的个数设为 ,容积设为
,则可以对问题规模进行变换:
- 把物体体积变换为
,背包容积变为
- 把
个体积为
的物体装到容积
的背包里,等价于装到容积
的背包里,所以取这两者的最小值作为背包容积
n, w, l, r, x, y, z = map(int, input().split())
mod = 998244353
def backpack(vol, nobj):
''' 容积 vol 的背包放入 nobj 件 [l, r] 体积的物体'''
# 转化问题规模
right, vol = r - l, vol - l * nobj
vol = min(vol, nobj * right - vol)
# 初始化状态表
dp = [1] + [0] * vol
for _ in range(nobj):
# 从大到小枚举, 避免重复计算
for v in filter(dp.__getitem__, range(vol, -1, -1)):
dp[v] %= mod
# 往容积更大的方向叠加
for vnew in range(min(v + right, vol), v, -1): dp[vnew] += dp[v]
return dp[-1] % mod
res, rows = 0, y - x
# 第 y 行之后的方案数
base = pow(r - l + 1, n - y, mod)
# 枚举第 x 行的积木数
for hx in range(w + (x - 1) * l, w + (x - 1) * r + 1):
hy = z * hx
if rows * l <= hy - hx <= rows * r:
# 第 x 行之前的方案数
cur = base * backpack(hx - w, max(0, x - 1)) % mod
# x ~ y 行的方案数
if cur: cur = cur * backpack(hy - hx, y - x) % mod
# 叠加方案数
res = (res + cur) % mod
print(res)
“123” 45🏆
【题目描述】
小蓝发现了一个有趣的数列,这个数列的前几项如下:1, 1, 2, 1, 2, 3, 1, 2, 3, 4, ...
小蓝发现,这个数列前 1 项是整数 1,接下来 2 项是整数 1 至 2,接下来 3 项是整数 1 至 3,接下来 4 项是整数 1 至 4,依次类推。
小蓝想知道,这个数列中,连续一段的和是多少。
【输入格式】
输入的第一行包含一个整数 T,表示询问的个数。
接下来 T 行,每行包含一组询问,其中第 i 行包含两个整数 ,表示询问数列中第
个数到第
个数的和。
【输出格式】
输出 T 行,每行包含一个整数表示对应询问的答案。
【样例】
输入 | 输出 |
3 1 1 1 3 5 8 | 1 4 8 |
【评测用例规模与约定】
10% | ![]() |
20% | ![]() |
40% | ![]() |
70% | ![]() |
80% | ![]() |
90% | ![]() |
100% | ![]() |
【解析及代码】
这个题是肯定要用等差数列的求和公式的:
- 正运算 sub_n:
- 逆运算 solve_n:
这个数列由无限个等差数列组成,记第 i 个数的位置为 ,以表示这个数在第
个等差数列末尾右移
的位置,则有:
利用逆运算 solve_n 则可以求出 ,利用这个方法对题目给定的
进行变换
import math
# 等差数列的正逆运算
solve_n = lambda x: int(math.sqrt(2 * x + 0.25) - 0.5)
sub_n = lambda n: (n + 1) * n // 2
for _ in range(int(input())):
l, r = map(int, input().split())
l -= 1
# i = sub_n(bound) + bias
l_bound, r_bound = map(solve_n, (l, r))
l_bias, r_bias = l - sub_n(l_bound), r - sub_n(r_bound)
# 左边界所在的数列 + 区间中的完整数列
ans = sum(sub_n(i) for i in range(l_bound + 1, r_bound + 1))
# + 因右边界少算的部分 - 因左边界多算的部分
print(ans + sub_n(r_bias) - sub_n(l_bias))
二进制问题 100🏆
【题目描述】
小蓝最近在学习二进制。他想知道 1 到 N 中有多少个数满足其二进制表示中恰好有 K 个 1。你能帮助他吗?
【输入格式】
输入一行包含两个整数 N 和 K。
【输出格式】
输出一个整数表示答案。
【样例】
输入 | 输出 |
7 2 | 3 |
【评测用例规模与约定】
30% | ![]() |
60% | ![]() |
100% | ![]() |
【解析及代码】
如果给定 k = 4, N = 1101111 (7 bit),记其方案数为 :
=
+
(固定第一个 1)
=
+
(固定第二个 1)
,以此类推
def comb(n, k):
if n < k: return 0
# 利用组合数的性质优化运算
x, k = 1, min(k, n - k)
for i in range(n, n - k, -1): x *= i
for i in range(k, 0, -1): x //= i
return x
n, k = map(int, input().split())
n = bin(n)[2:]
ans = 0
while len(n) >= k:
ones = n.count('1')
# 全都是 1, 省事
if ones == len(n):
ans += comb(ones, k)
break
# 第一位是 1, 固定这个 1
if n[0] == '1':
ans += comb(len(n) - 1, k)
k -= 1
# 第一位是 0, 跳过
n = n[1:]
print(ans)
大写 100🏆
【题目描述】
给定一个只包含大写字母和小写字母的字符串,请将其中所有的小写字母转换成大写字母后将字符串输出。
【输入格式】
输入一行包含一个字符串。
【输出格式】
输出转换成大写后的字符串。
【样例】
输入 | 输出 |
LanQiao | LANQIAO |
【评测用例规模与约定】
100% | 字符串的长度不超过 100 |
【解析及代码】
print(input().upper())
巧克力 36🏆
【题目描述】
小蓝很喜欢吃巧克力,他每天都要吃一块巧克力。
一天小蓝到超市想买一些巧克力。超市的货架上有很多种巧克力,每种巧克力有自己的价格、数量和剩余的保质期天数,小蓝只吃没过保质期的巧克力,请问小蓝最少花多少钱能买到让自己吃 x 天的巧克力。
【输入格式】
输入的第一行包含两个整数 x, n,分别表示需要吃巧克力的天数和巧克力的种类数。
接下来 n 行描述货架上的巧克力,其中第 i 行包含三个整数 ,表示第 i 种巧克力的单价为
,保质期还剩
天(从现在开始的
天可以吃),数量为
。
【输出格式】
输出一个整数表示小蓝的最小花费。如果不存在让小蓝吃 x 天的购买方案,输出 -1。
【样例】
输入 | 输出 | 说明 |
10 3 1 6 5 2 7 3 3 10 10 | 18 | 第 1 种买 5 块,第 2 种买 2 块,第 3 种买 3 块。 前 5 天吃第 1 种, 第 6、7 天吃第 2 种, 第 8 至 10 天吃第 3 种 |
【评测用例规模与约定】
30% | ![]() |
100% | ![]() |
【解析及代码】
首先要确保 x 天内都有巧克力可以吃,其次再求花钱最少的方案
所以要从后往前制定巧克力的购买计划,先把巧克力按照保质期排序 (从大到小),初始令时间为 x:
- 查找所有保质期 ≥ x 的巧克力 (按价格排序放进“购物车”),然后把 < x 的第一种巧克力的保质期记为 exp
- 在“购物车”里选择最便宜的巧克力并购买,每购买 n 个,x 也减少 n,直到 x = exp 时停止,开始“进货”,然后重复以上步骤
上面这个“购物车”用继承 list 的类 Sequence 实现,重写 append 方法使其能够按序插入新的巧克力
而巧克力的类 Chocolate 主要利用 buy 函数,实现快捷的属性更改
from collections import deque
class Chocolate:
def __init__(self):
self.price, self.exp, self.num = map(int, input().split())
def __bool__(self):
return bool(self.num)
def buy(self, n):
self.num -= n
return self.price * n
class Sequence(list):
def append(self, obj):
super().append(obj)
self.sort(key=lambda i: i.price)
x, n = map(int, input().split())
chos = [Chocolate() for _ in range(n)]
chos.sort(key=lambda ch: ch.exp, reverse=True)
chos = deque(chos)
cost, pool = 0, Sequence()
# 检验是否有购买方案
if sum(ch.num for ch in chos) >= x:
while x > 0:
# 添加当前能购买的巧克力
while chos and chos[0].exp >= x: pool.append(chos.popleft())
if not pool: break
# 下一个需要考虑的保质期
exp = chos[0].exp if chos else 0
# 购买其中最便宜的巧克力
while pool and x != exp:
tar = pool[0]
n = min(tar.num, x - exp)
x -= n
cost += tar.buy(n)
if not tar: pool.pop(0)
print(cost if x == 0 else -1)
和与乘积 36🏆
【题目描述】
给定一个数列 ,问有多少个区间
满足区间内元素的乘积等于他们的和,即
。
【输入格式】
输入第一行包含一个整数 n,表示数列的长度。
第二行包含 n 个整数,依次表示数列中的数 。
【输出格式】
输出仅一行,包含一个整数表示满足如上条件的区间的个数。
【样例】
输入 | 输出 | 说明 |
4 1 3 2 2 | 6 | 符合条件的区间为 [1, 1], [1, 3], [2, 2], [3, 3], [3, 4], [4, 4] |
【评测用例规模与约定】
20% | ![]() |
50% | ![]() |
100% | ![]() |
【解析及代码】
因为区间可以只包含一个数,所以直接初始化 ans = 数列长度
根据 ,分析和 sum 与乘积 prod 可能发生的变化:
- 当 prod ≥ 2,
将使 prod - sum 增大
- 当 prod = 1,
将使 prod - sum 减小 1
- 当 prod ≥ 1,
将使 prod - sum 减小 1
因为累乘是比较危险的操作,乘积 prod 可能会快速到达一个非常大的值 (prod ≠ 1),所以利用上述的规律进行剪枝 (规律 3):对于区间 ,如果此时 prod - sum > n - R,就算剩下的 n - R 个数全为 1,也不可能使 prod = sum
然后就是个加了个剪枝的暴力枚举
ans = int(input())
array = list(map(int, input().split()))
for l in range(len(array)):
sum_, prod = array[l], array[l]
for r in range(l + 1, len(array)):
sum_ += array[r]
prod *= array[r]
# 如果相等
if sum_ == prod: ans += 1
# 验证是否有救 (假设剩下的都是 1)
if prod - sum_ >= len(array) - r: break
print(ans)