2023/03/25
蓝桥杯在即,我居然现在才开始刷y总的每日一题。每天更新,争取在蓝桥杯2023/04/08之前刷完!这篇博客主要是记录自己的刷题历程。参赛语言:python3。
做题记录
2023/03/25
week1的星期一、星期二
2023/03/26
week1的星期三~星期五,week2的星期一
2023/03/27
week2的星期二,星期三没写完
2023/03/28
week2的星期三写完,星期四没写完(被裁剪序列卡住了)
2023/03/29
因为这个题标注了前缀和的知识点,所以我就努力往这个方向想(菜菜)。我想应该先用 O ( n ) O(n) O(n) 的时间算出前缀和数组,然后用二重循环去遍历两个数组交界点的位置,已知前缀和数组则可以用 O ( 1 ) O(1) O(1) 计算出三段子数组的元素和,如果三段都相等,则计数+1。
但是数据范围是 1 0 5 10^5 105 ,盲猜大概率会超时。果然超时了。滚去看y总的题解。
y总技巧:根据数据范围去看标准解法的时间复杂度。比如这题,数据范围是 1 0 5 10^5 105 ,则时间复杂度应该是 O ( n l o g n ) O(nlogn) O(nlogn)。
y总的技术分享: 由数据范围反推算法复杂度以及算法内容
由于时间复杂度最多为 O ( n l o g n ) O(nlogn) O(nlogn) ,而枚举2个点时间复杂度是 O ( n 2 ) O(n^2) O(n2) ,所以最多只能枚举一个点。y总说按经验一般是枚举第2个点。枚举过程中,当给第2个点一个确定值 j j j 之后,满足条件的方案数是从第1个点到第 j − 1 j-1 j−1 个点中,前缀和的值为 s [ n ] / / 3 s[n]//3 s[n]//3 的点数,其中 s [ n ] s[n] s[n] 是整个数组的元素总和。但是每次求方案数,不需要遍历第一个点,因为随着第2个点 j j j 往后移动1次,此时的方案数与上一次的方案数是有关联的,仅仅取决于第 j − 1 j-1 j−1 个点的情况。(感觉讲不太清,看代码吧)
N = 100010
s = [0]*N # 前缀和数组
n = int(input())
a = [int(x) for x in input().split()] # 原始数组
a = [0]+a # 下标从1开始
# 前缀和模板
for i in range(1, n+1):
s[i] = s[i-1] + a[i]
if s[n]%3!=0: print(0)
else:
res = 0; cnt = 0
for j in range(2, n): # 第一段、第三段非空
if s[j-1]==s[n]//3: cnt+=1
if s[j]==s[n]//3*2: res+=cnt
print(res)
看题目脑袋空空,回顾了一下差分的知识点,差分的作用是用 O ( 1 ) O(1) O(1) 的时间完成对原始数组 [ l , r ] [l,r] [l,r] 内的每个元素都+1。尝试想了想思路:每次操作都往数组尾部插入一个0,则最终数组V是n个元素0。将位于数组 V 末尾的 a i a_i ai 个元素都变为 1,也就是将区间 [ i − a i + 1 , i + 1 ] [i-a_i+1, i+1] [i−ai+1,i+1] 内的元素变为1。想到差分模板,只要每次都把区间 [ i − a i + 1 , i + 1 ] [i-a_i+1, i+1] [i−ai+1,i+1] 内的元素+1,最后看该位置的元素是否大于0即可。
写的时候连样例都没过,菜菜。本来以为是思路有问题,看了y总的视频讲解,发现也是这思路,是我中间写错了。
N = 200010
T = int(input())
for _ in range(T):
n = int(input())
a = [int(x) for x in input().split()] # 操作数组
a = [0]+a
# 差分操作
b = [0]*(n+2) # 差分数组
for i in range(1,n+1):
a[i] = min(a[i], i) # 本来是这一步写错了
b[i-a[i]+1] += 1
b[i+1] -= 1
# 求前缀和模板
x = [0]*(n+1)
for i in range(1, n+1):
x[i] = x[i-1]+b[i]
# 转化为答案
res = [0]
for i in range(1, n+1):
if x[i]>0: res.append(1)
else: res.append(0)
print(' '.join(map(str, res[1:])))
memset(b, 0, (n+1)*4)
更节约时间复杂度,否则易超时。print("%d", !!x[i])
。用!!
操作,可以在0和1之间互相转换。没看懂这道题跟二分有什么关系,居然还是简单题。菜菜。
转化题意:找到一个最小长度K,使得字符串中任何一个长度为k的子串都不相同。(这个我都没想到 )
解法一:暴力。因为 N < = 100 N<=100 N<=100 ,所以四重循环的时间复杂度是 O ( n 4 ) O(n^4) O(n4) ,也不会超时,卡着能过。而且实际的时间复杂度没有这么高,因为有3重循环不需要枚举n次,循环中还有很多break。(暴力做法都没想到的蒟蒻本人 )
解法二:二分+哈希,可以把时间复杂度下降到 O ( n 2 l o g n ) O(n^2logn) O(n2logn)。
暴力:
# 暴力
n = int(input())
s = input()
for k in range(1, n+1): # k: 1-n
flag = False # 是否有2个相同的子串
for i in range(n-k+1): # i:0-n-1
for j in range(i+1, n-k+1):
same = True
for u in range(k):
if s[i+u]!=s[j+u]:same=False; break
if same==True: flag=True; break
if flag==False: print(k); break
二分+哈希:
# 二分+哈希
def check(k): # 判断长度为k的子串有无重复
global n
st = set()
for i in range(n-k+1):
if s[i:i+k] in st: return False
st.add(s[i:i+k])
return True
if __name__=='__main__':
global n
n = int(input())
s = input()
l, r = 1, n
while l<r:
mid = (l+r)//2
if check(mid): r=mid
else: l=mid+1
print(r)
虽然是个简单题,但也是自己ac的,开心!
题目只给了一个字符串,所以显然是2个指针去遍历同一个字符串,虽然数据范围很小,但是争取达到 O ( n ) O(n) O(n) 的时间复杂度。思路是,指针 i i i 存连续x子串的起始位置,指针 j j j 存连续x子串的结束位置。每次得到一段x子串后,要删去的x字符个数是该子串的长度-2。
n = int(input())
s = input()
i, j, cnt = 0, 0, 0
while i<n and j<n:
while j+1<n and s[j+1]=='x' and s[i]=='x':
j+=1
cnt += max(0, j-i+1-2)
i=j+1; j=i
print(cnt)
读完题目,os:递推是啥(蒟蒻)。
题解思路:最后的结果是,砖块都能变成白色、或者黑色、或者无解。假设从第1块砖开始判断,如果第1块砖满足要求,则不变色;如果不满足,则反转第1、2块砖。这个操作可能会影响第2块砖的颜色。但是我们可以从前往后对每一块砖进行判断,是否需要变色。为了使倒数第2块砖满足要求,所以最后一块砖不能单独操作。如果最后一块砖的颜色和目标色一致,则找到了满足条件的解。
就算每个砖块都需要反转,总操作数为 n-1,不会超过 3n 的限制。
为了记录合理方案,可以开一个数组/列表,每次发生反转,就把下标存进去。
def update(c):
if c=='W': return 'B'
return 'W'
def check(c): # 尝试把砖块全变成颜色c
global n
k = 0
ops = []
q = list(a)
for i in range(n-1):
if q[i]!=c:
q[i] = update(q[i]); q[i+1] = update(q[i+1]); k+=1; ops.append(i+1)
if q[n-1]!=c: return False
print(k)
if k>0: print(' '.join(map(str, ops)))
return True
if __name__=='__main__':
T = int(input())
for _ in range(T):
global n
n = int(input())
a = input()
if not check('B') and not check('W'): print(-1)
看到题目,甚至都不记得前序、后序、中序遍历是啥了,菜菜。
前序 / 中序 / 后序遍历:指的都是根节点的遍历顺序,左子树一定在右子树的前面。
整体思路是由后序遍历和中序遍历的序列构建出树,然后可以用bfs找出层序遍历结果,或者在构建树的过程中保存每一层的所有节点。
首先人工模拟一下构建树的过程:后序遍历中,最后一个节点一定是整棵树的根节点。由于题目中说每个节点的权值不同,所以可以在中序遍历序列中找到对应根节点的位置,此时它左边的节点都是它的左子树,它右边的节点都是它的右子树。再根据中序遍历中,左子树和右子树序列的长度,在后序遍历中也划分出左子树和右子树。以此类推,用递归分别对左子树区间和右子树区间去重复上述操作,直到构建出整棵树。
法一:保存每一层的节点。递归的参数比较多(本菜鸟是想不出来 ),包括中序序列和后序序列的左右端点,此时递归的子树深度。每次在后序序列中找到根节点的权值后,需要在中序序列找到对应的位置,所以新开一个数组p[]
,来记录中序序列中每个权值对应的下标。还需要开一个二维列表,来存每一个深度的所有节点。递归结束的条件就是左端点的值大于右端点,每次递归做的操作就是把该层的根节点保存在二维列表里。
法二:bfs。与法一不同的是,这里需要存储树,用2个列表l[]
和r[]
,分别存对应节点的左孩子和右孩子。递归过程中是保存左右孩子。bfs用一个队列deque实现。
法一:保存每一层的节点。
N = 35
post, mid, p = [], [], [0]*N
level = [[0] for _ in range(N)] # 每一层的所有节点
def build(pl, pr, ml, mr, d):
if pl>pr: return
val = post[pr] # root
k = p[val]
level[d].append(val)
build(pl, pl+k-ml-1, ml, k-1, d+1)
build(pl+k-ml, pr-1, k+1, mr, d+1)
if __name__=='__main__':
n = int(input())
post = [int(x) for x in input().split()]
mid = [int(x) for x in input().split()]
for i in range(n):
p[mid[i]] = i
build(0, n-1, 0, n-1, 0)
# print
for l in level:
if len(l)>1:
print(' '.join(map(str, l[1:])),end=' ')
法二:bfs。
from collections import deque
N = 35
post, mid, p = [], [], [0]*N
l, r = [0]*N, [0]*N
def build(pl, pr, ml, mr, d):
if pl>pr: return 0
val = post[pr] # root
k = p[val]
l[val] = build(pl, pl+k-ml-1, ml, k-1, d+1)
r[val] = build(pl+k-ml, pr-1, k+1, mr, d+1)
return val
def bfs(x):
q = deque()
res = []
q.append(x)
while q:
t = q.popleft()
res.append(t)
if l[t]>0: q.append(l[t])
if r[t]>0: q.append(r[t])
print(' '.join(map(str, res)))
if __name__=='__main__':
n = int(input())
post = [int(x) for x in input().split()]
mid = [int(x) for x in input().split()]
for i in range(n):
p[mid[i]] = i
build(0, n-1, 0, n-1, 0)
bfs(post[n-1]) # 从根节点开始bfs
是普通的并查集模板,无脑写模板。但是它卡输入,要写stdin.readline()
。
from sys import *
N = 20010
p = list(range(N))
def find(x):
if x!=p[x]: p[x] = find(p[x])
return p[x]
def merge(a, b):
pa = find(a); pb = find(b)
if pa!=pb: p[pa] = pb
if __name__=='__main__':
n, m = map(int, stdin.readline().split())
for _ in range(m):
a, b = map(int, stdin.readline().split())
if a!=b: merge(a, b)
q = int(stdin.readline())
for _ in range(q):
c, d = map(int, stdin.readline().split())
pc = find(c); pd = find(d)
print('Yes' if pc==pd else 'No')
stdin.readline()
,而且要导入库from sys import *
,否则会超时。读完题,我觉得这数据范围好大,都开到 1 0 9 10^9 109 了,而且对应的二进制数应该 < = 32 <=32 <=32 位,我觉得int肯定存不了。也不知道怎么哈希,于是寄。
y总说这题数据范围很小,因为二进制数可以用字符串存,32位就显得很小了(我居然完全想反了 orz)。因为题干说有且仅有1位数写错,所以二进制数最多也就枚举32次(二进制位不是0就是1),三进制数最多枚举30*2次(比30次应该还小一点)。题目说存在唯一解,那把所有二进制数和三进制数转换成十进制,只会有一个数出现2次。python可以用set来做哈希表,判断这个数是不是出现过。
法一:直接用set表示哈希表。
from sys import *
import copy
def base(s, b): # 把b进制的字符列表s转换成十进制数返回
nums = 0; l = len(s)
for i in range(l):
nums = nums * b + ord(s[i])-ord('0')
return nums
if __name__=='__main__':
s2 = list(input()); s3 = list(input())
l2 = len(s2); l3 = len(s3)
st = set()
for i in range(l2):
str = copy.deepcopy(s2); str[i] = '0' if str[i]=='1' else '1'
if len(str)>1 and str[0]=='0': continue # 避免前导0
st.add(base(str, 2))
flag = False
for i in range(l3):
for j in ['0','1','2']:
str = copy.deepcopy(s3)
if str[i]!=j:
str[i] = j
if len(str)>1 and str[0]=='0': continue # 避免前导0
x = base(str, 3)
if x in st: print(x); flag = True; break
if flag==True: break
法二:手写哈希表。
from sys import *
import copy
N = 103
h = [-1]*N
def find(x): # 开放寻址法: 找到x的哈希位置t
t = x%N
while h[t]!=-1 and h[t]!=x:
t+=1
if t==N: t = 0
return t
def base(s, b): # 把b进制的字符串s转换成十进制数返回
nums = 0; l = len(s)
for i in range(l):
nums = nums*b + ord(s[i])-ord('0')
return nums
if __name__=='__main__':
s2 = list(input()); s3 = list(input())
l2 = len(s2); l3 = len(s3)
for i in range(l2):
str = copy.deepcopy(s2); str[i] = '0' if str[i]=='1' else '1'
if len(str)>1 and str[0]=='0': continue
x = base(str, 2)
h[find(x)] = x
flag = False
for i in range(l3):
for j in ['0','1','2']:
str = copy.deepcopy(s3)
if str[i]!=j:
str[i] = j
# print(len(str),' ', str[0])
if len(str)>1 and str[0]=='0': continue
x = base(str, 3)
# print('x = ', x)
if h[find(x)]!=-1: print(x); flag = True; break
if flag==True: break
>=数据范围
的最小质数,比如这道题是100003。补一下字符串哈希的模板题:
字符串哈希好难,初学时直接无脑背模板,隔了20天再来写模板题,带了些自己的思想,就报错一大堆,又是超时,又是超内存。以下是我犯过的错误:
假设我们把前缀哈希存到数组h[]
中。
ord(str[i])
,不需要再减去什么ord('0')
。p[]
数组里。p[]
和计算子串的哈希值时,也需要对 Q Q Q 取模。不然会超内存。感觉取模在很大程度上是避免超内存的。字符串哈希代码
maxn = 100010; P = 131; Q = 2**64
h, p = [0]*maxn, [1]*maxn
def compute(l, r):
return (h[r]-h[l-1]*p[r-l+1]) % Q
if __name__=='__main__':
n, m = map(int, input().split())
str = ' ' + input()
# 前缀哈希
for i in range(1, n+1):
h[i] = (h[i-1]*P + ord(str[i]))%Q
p[i] = p[i-1]*P%Q
for _ in range(m):
l1, r1, l2, r2 = map(int, input().split())
if compute(l1, r1)==compute(l2, r2): print('Yes')
else: print('No')
这道模板题如果用dict去做就会TLE。
没思路,看到它是困难题心生畏惧。
看完题解发现还是很难,dp + 双指针 + 单调队列 + multiset(python里还没有这玩意)。
理解一下题意:给定一个序列,要求分成若干段,每段的元素和 首先,结果不存在的情况,是序列中任意一个元素值>=M。其他情况都能算出结果。 使用Dp分析法: 如果用朴素dp来做,应该是一个三重循环,第一重循环枚举所有的 i i i,第二重循环枚举最后一段的长度 k k k,第三重循环把最后一段遍历一次,求段中的最大值。时间复杂度是 O ( n 3 ) O(n^3) O(n3)。本题数据范围是 1 0 5 10^5 105 级别,因此时间复杂度要优化到 O ( n l o g n ) O(nlogn) O(nlogn)。 接下来是dp优化:
代码