两天速通蓝桥杯
其实是标题党,断断续续准备了不到一个月吧,但是真的是菜比,没有提示做不出来的那种,今天是4.11号,4.13号就要打比赛了,准备把一些临考场能用到的重点整理一下,如果我考完我还有心情写这个笔记,我再复盘一下
很建议提前看一下这两个帖子:
蓝桥杯Python A组国一经验分享(希望这篇文章可以给你一点点帮助)_蓝桥杯国赛一二三等奖比例-CSDN博客
十二天艰难速成蓝桥杯Orz(算法+习题合集)_备战蓝桥杯要多长时间-CSDN博客
把重难点和一些技巧总结的蛮清楚的。
tips
1.建议考前适应一下IDLE
2.递归深度
背下来sys.setre,然后用Tab键补齐就行
3.excel技巧
【蓝桥杯小技巧】暴力+ Excel的使用(持续更新)_蓝桥杯巧用excel-CSDN博客
读入
之前一直用C,到了python这有点不知道怎么读入
(1)字典
dic = dict()
for _ in range(n):
ts,id = map(int,input().split())
#这样会得到{id:ts}
dic[id] = dic.get(id,[]) + [ts]
#遍历字典
for j in dic.items():
#这样得到ids为key,ts为冒号后面的内容
ids,ts_ = (i for i in j)
(2)优先队列
from queue import PriorityQueue
q = PriorityQueue()
#入队
q.put(x)
#出队
x = q.get()
#判断队列是否为空
len(q.queue) != 0
(3)邻接表:
邻接表里存了下一个节点和权重的
G = [[] for i in range(n+1)]
for _ in range(m):
u,v,w = map(int,input().split())
G[u].append([v,w])
(4)[1,2,…n]
p = list(range(n+1))
(5)开平方
int(sqry(a))
考试流程解惑
因为我没去线下测试环境,所以对准考证上的比赛流程有点疑惑,下面这篇给的挺详细的
蓝桥杯攻略!省一经验+考试全流程+技巧分享_蓝桥杯可以插u盘吗-CSDN博客
考完之后来现身说法,其实没啥,我们学校的流程就是去考点,找到自己的座位号坐下,然后考试开始前可以操作电脑,查看一下环境(这个因学校而异),我的考点的电脑就有vscode,我全程就用vscode操作的,有的考点只有IDLE,所以熟悉一下IDLE也没毛病。然后基本九点提前一点点,那个考试界面可以下载试题了,你就下载那个压缩包,然后监考老师会告诉你压缩包的密码,你打开就是试题。
下面是一些重要的考点笔记:
搜索
重点!!!DFS BFS,可以暴力求解
DFS
流程:
1.将所有顶点标记为未访问
2.选择一个起始顶点v,访问v
3.选择v的任意一个未被访问的顶点w,访问w,v<-w
4.重复上述步骤3,直到某一节点的所有邻接点都已经被访问,v<-返回到此节点的上一层结点
5.重复步骤3和4,直到返回到根结点,且根节点所有的邻接点都已经被访问
DFS的节点有先序号和后序号两个属性,访问每个结点的时候赋一个先序号,访问该结点结束,要回溯到上一个结点的时候,赋一个后序号
拆分数字
import os
import sys
def dfs(depth,num):
if depth == n + 1:
if sum(num) == x:
print(*num[1:],sep = ' ')
return
for i in range(1,x):
#要求这个数比前一个数大
if i > num[depth-1]:
num[depth] = i
dfs(depth+1,num)
n,x = map(int,input().split())
num = [0] * (n+1)
dfs(1,num)
分糖果
import os
import sys
ans = 0
#x,y分别为两种糖果剩余的数量
def dfs(depth,x,y):
global ans
if depth == 7:
if x == 0 and y == 0:
ans += 1
return
#第一种糖果可能的数量
for i in range(0,6):
if i <= x:
#第二种糖果可能的数量
for j in range(0,6):
if j <= y and 2 <= i + j <= 5:
dfs(depth+1,x-i,y-j)
dfs(0,9,16)
print(ans)
回溯
小朋友崇拜圈
import os
import sys
n = int(input())
kid =list(map(int,input().split()))
vis = [0] * (n+1)
#用于记录最长路径
len = 0
def dfs(depth,x):
global len
vis[x] = depth
if vis[kid[x]]:
if vis[x] - vis[kid[x]] + 1 > len:
len = vis[x] - vis[kid[x]] + 1
return
else:
dfs(depth+1,kid[x])
for i in range(1,n+1):
if vis[i] == 0:
dfs(1,i)
print(len)
BFS
广度有限搜索从起始节点v出发,访问邻接于v的所有顶点,接着从v的一个邻接点出发,访问此节点所有未被访问过的邻接点,依次对v结点的其他邻接点进行类似的操作
利用队:先进先出
1、创建一个空队列queue(用来存放节点)和一个空列表visit(用来存放已访问的节点)
2、依次将起始点及邻接点加入queue和visit中
3、poo出队列中最先进入的节点,从图中获取该节点的邻接点
4、如果邻接点不在visit中,则将该邻接点加入queue和visit中
5、输出pop出的节点
6、重复3、4、5,直至队列为空
最少操作数
import os
import sys
from collections import deque
vis = [-1] * 100001
def bfs(n,k):
q = deque()
q.append(n)
cnt = 0
vis[n] = 0
while len(q) != 0:
i = q.popleft()
if i == k:
return vis[k]
for j in [i+1,i-1,i*2]:
#这里加一个判断j是否越界
if 1<= j <= 100000 and vis[j] == -1:
vis[j] = vis[i] + 1
q.append(j)
n,k = map(int,input().split())
print(bfs(n,k))
记忆化搜索
原始:
def f(x):
if x == 0 or x == 1:
return 1
return f(x-1) + f(x-2)
记忆化:
dic = {0:1,1:1}
def f(x):
if x in dic.keys():
return dic[x]
dic[x] = f(x-1) + f(x-2)
return dic[x]
使用stl:
from functools import lru_cache
@lru_cache(maxsize = None)
def f(x):
if x == 0 or x == 1:
return 1
return f(x-1) + f(x-2)
最短路
floyd
复杂度太高,但是胜在简单
蓝桥公园
import os
import sys
n,m,q = map(int,input().split())
INF = int(1e18)
dist = [[INF] * (n+1) for _ in range(n+1)]
for _in range(m):
u,v,w = map(int,input().split())
dist[u][v] = w
dist[v][u] = w
def floyd():
for k in range(1,n+1):
for i in range(1,n+1):
for j in range(1,n+1):
dist[i][j] = min(dist[i][j],dist[i][k] + dist[k][j])
for _ in range(q):
st,ed = map(int,input().split())
if dist[st][ed] != INF:
print(dist[st][ed])
else:
print(-1)
dijkstra
最常用,记下来!
模板
import os
import sys
from queue import PriorityQueue
def dijstra(s):
q = PriorityQueue()
q.put((s,0))
while len(q.queue())!= 0:
u,_ = q.get()
if vis[u] != 0:
continue
vis[u] = 1
for v,w in G[u]:
d[v] = min(d[v],d[u] + w)
q.put((v,d[v]))
for i in range(n+1):
if d[i] == INF:
d[i] = -1
print(*d[1:],sep=' ')
n,m = map(int,input().split())
G = [[] for i in range(n+1)]
INF = int(1e18)
d = [INF] * (n+1)
vis = [0] * (n+1)
for _ in range(m):
u,v,w = map(int,input().split())
G[u].append([v,w])
dijstra(1)
bellman-ford
有负边再用,应该用不到
模板
import os
import sys
def bellman_ford():
for _ in range(n-1):
for u,v,w in e:
#如果v是第n个城市,就不需要隔离
if v != n:
res = c[v]
else:
res = 0
if dist[v] = dist[u] + w + res:
dist[v] = dist[u] + w + res
#第n次遍历
for u,v,w in e:
#这里要判断,如果是第n个城市,就不需要隔离
if v != n:
res = C[v]
else:
res = 0
if dist[v] > dist[u] + w + res:
return -1
n,m, = map(int,input().split())
c = [0] + list(map(int,input().split()))
INF = int(1e18)
e = []
dist = [INF] * (n+1)
for _ in range(m):
u,v,c = map(int,input().split())
e.append((u,v,c))
if bellman_ford() == -1:
print(-1)
else:
print(dist[n])
模版题
P3371 【模板】单源最短路径(弱化版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
最小生成树
kruskal
适用于稀疏图
例
def findroot(x):
if x == p[x]: return x
p[x] = findroot(p[x])
return p[x]
def kruskal():
#遍历每一条边
for w,u,v in e:
#查看加入这条边是否构成环
rootu,rootv = findroot(u),findroot(v)
if rootu != rootv:
p[rootu] = rootv
sum += 1
max_val = max(max_val,w)
print(sum,max_val)
n,m, = map(int,input().split())
e = []
for _ in range(m):
u,v,w = map(int,input().split())
e.append((w,u,v))
#按照边的权重大小排序
e.sort()
#并查集中存储父节点
p = list(range(n+1))
sum,max_val = 0
kruskal()
prim
适用于稠密图
例
同上面kruskal是一个题
#这里发现一个规律,一般加边的都用e.appen(u,v,w)来存储,如果是加点的一般用邻接表或邻接矩阵
n,m, = map(int,input().split())
INF = int(1e18)
G = [[INF] * (n+1) for i in range(n+1)]
for _ in range(m):
u,v,w = map(int,input().split())
G[u][v] = G[v][u] = min(G[u][v],w)
d = [INF] * (n+1)
max_val = 0
for i in range(n):
d[u] = 0
next_u,next_val = INF
for v in range(1,n+1):
#如果是已经加入到集合中的点
if d[v] == 0: continue
#如果新加入的点u更新了到点v的最小边,则更新
d[v] = min(d[v],G[u][v])
#保存最小边,并且点v作为下一条加入集合的点
if d[v] < next_val:
next_val = d[v]
next_u = v
u = next_u
max_val = max(max_val,next_val)
print(n-1,max_val)
模板题
[P2330 SCOI2005] 繁忙的都市 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
P1195 口袋的天空 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
树状数组
较难
引出:对于数组求和,我们可以用两两组成一个和的形式储存下来,如下图所示:
如果我们需要求前四个数的和,直接就可以得到19
如果要求前3个数的和,就14+1
但其实图中有些数据是无用的,比如14的和5,因为求三个数的和和求四个数的和都用不到他。上述数组中无用的部分还有很多,所有层的第偶数个数字都是无用的
然后我们观察剩下的数据,发现剩下的数据正好有n个,我们可以把这些有用的数据都装进一个数组中,这个数组和原始的数据正好一样长
求和时,我们只需找到对应的区间,将这些区间相加就可以得到答案
lowbit
观察树状数组第一行数据
观察第二行
总结:
例
def lowbit(x):
return x & (-x)
def update(x,A):
while x <= n:
tree[x] += A
x += lowbit(x)
def query(x):
sum = 0
while x:
sum += tree[x]
x -= lowbit(x)
return sum
n = int(input())
a = [0] + list(map(int,input().split()))
m = int(input())
q = []
#初始化树状数组
tree = [0] * (n+1)
for i in range(1,n+1):
update(i,a[i])
for _ in range(m):
q = list(map(int,input().split()))
if q[0] == 1:
update(q[1],q[2])
# print(a)
elif q[0] == 2:
print(query(q[2]) - query(q[1] - 1))
数论
gcd
1.gcd:利用辗转相除法(欧几里得算法)来求最大公约数。
a,b均能整除gcd(a,b)得到a1,b1;
a1,b1均能整除gcd(a1,b1)得到a2,b2;
……
an-2,bn-2均能整除gcd(an-2,bn-2)得到an-1,1;
最后一次取模为an,0,一遍遍递归即可得到最初a,b的最大公约数
#非递归
def gcd(a,b):
while b != 0:
a,b = b, a % b
return a
#非递归
def gcd(a,b):
if b == 0: return a
return gcd(b,a%b)
扩展的欧几里得
def Exgcd(a,b):
if b == 0:
return a,1,0
d,x,y = Exgcd(b,a%b)
return d,y,x - (a//b) * y
lcm
lcm就是最小公倍数 其实就是a * b / gcd(a,b)
def lcm(a,b):
res = a / gcd(a,b)
return res * b
素数筛法
def isPrime(a):
if a < 2:
return False
for i in range(2,int(sqry(a)) + 1):
if a % i == 0:
return False
return True
DP
挺重点的,但是有点考思维
步骤:
1.拆分子问题
2.确定状态,如dp[x]表示上x个台阶的方案数
3.确定状态转移方程
上台阶
import os
import sys
MOD = 10000000007
def louti():
for i in range(1,n+1):
if i == 1:
if i not in a:
dp[i] = 1
continue
if i in a:
continue
else:
dp[i] = (dp[i-1] + dp[i-2]) % MOD
n,m = map(int,input().split())
a = list(map(int,input().split()))
print(a)
dp = [0] * (n+1)
dp[0] = 1
louti()
print(dp[n])
选数异或
异或值的结果最大为63,下面注意一下范围:
原问题:给定n个正整数a,子序列异或为x的方案数
子问题:前i个整数,子序列异或为j的方案数
第i个正整数:选择/不选第i个数字,如何得到dp[i] [j]
不选的话就是dp[i-1] [j] -> dp[i] [j]
选择第i个数字,则就是从第i-1个得到的异或结果xxx 异或 a[i]得到 j
根据异或的性质 xxx等于 j 异或 a[i]
import os
import sys
MOD = 998244353
n,x = map(int,input().split())
a = list(map(int,input().split()))
dp = [[0] * 64 for _ in range(n+1)]
#出口
dp[0][0] = 1
for i in range(n+1):
for j in range(64):
dp[i][j] = (dp[i-1][j] + dp[i-1][j ^ a[i]]) % MOD
print(dp[n][x])
最长公共子序列
import os
import sys
n,m = map(int,input().split())
a = [0]+list(map(int,input().split()))
b = [0]+list(map(int,input().split()))
dp = [[0] * (m+1) for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,m+1):
if a[i] == b[j]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j],dp[i][j-1])
print(dp[n][m])
输出最长子序列:
path = []
i,j = N,M
while i != 0 and j != 0:
#print(i,j)
if dp[i][j] == dp[i-1][j]:
i = i - 1
elif dp[i][j] == dp[i][j-1]:
j = j - 1
else:
i,j = i - 1, j - 1
path.append(a[i])
#倒着打印
print(path[::-1])
01背包
import os
import sys
N,V = map(int,input().split())
dp = [[0] * (V+1) for x in range(N+1)]
for i in range(1,N+1):
w,v = map(int,input().split())
for j in range(V+1):
if j >= w:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v)
else:
dp[i][j] = dp[i-1][j]
#print(dp)
print(dp[N][V])
滚动数组优化:
魔法背包
import os
import sys
n,m,K= map(int,input().split())
dp = [[0] * (2) for i in range(m+1)]
for i in range(n):
w,v = map(int,input().split())
for j in range(m,w-1,-1):
for k in range(2):
if k == 1 and j >= w + K:
dp[j][k] = max(dp[j][k],dp[j-w][k] + v,dp[j-K-w][0] + 2 * v)
else:
dp[j][k] = max(dp[j][k],dp[j-w][k] + v)
print(max(dp[m][0],dp[m][1]))
分组背包
例
import os
import sys
N,V = map(int,input().split())
dp = [[0] * (V+1) for i in range(N+1)]
for i in range(1,N+1):
s = int(input())
for k in range(s):
w,v = map(int,input().split())
for j in range(V+1):
if j < w:
#这里注意,由于是一组中选一个,因此上一轮的dp[i][j](本组中的上一个)是有用的,因此要和新的作比较
dp[i][j] = max(dp[i-1][j],dp[i][j])
else:
dp[i][j] = max(dp[i-1][j],dp[i-1][j-w]+v,dp[i][j])
print(dp[N][V])
区间DP
import os
import sys
INF = int(1e18)
n = int(input())
a = [0] + list(map(int,input().split()))
dp = [[INF]*(n+1) for _ in range(n+1)]
#还要声明一个前缀和数组
s = [0] * (n+1)
for i in range(1,n+1):
#用于合并1到i堆石子的花费
s[i] = s[i-1] + a[i]
#因此合并i到j堆的花费为s[j] - s[i-1]
dp[i][i] = 0
#遍历所有区间长度
for len in range(2,n+1):
#遍历所有左端点
i = 1
while i + len - 1 <= n:
#确定右端点
j = i + len - 1
#遍历子区间
for k in range(i,j):
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+1][j] + s[j] - s[i-1])
i += 1
print(dp[1][n])
字符串
KMP
首先我们找到了一个失配点,i不动,我们改变j:我们找到S的一个后缀(灰色括号)与T的一个前缀(红色括号)尽可能的匹配(最长),然后我们的j就可以放到红色括号末尾那里,从已经匹配的前缀后开始匹配
我们要求出一个Next数组(较复杂)
只要不匹配,j = next[j],因为next[j]已经储存了最长公共前后缀的长度
问题变成如何求next数组,next数组使用动态规划来求解:
我们先看左边的情况
j = next[i] T[0]到T[i-1]的最长后缀上面的浅绿色框,和T的前缀下面的浅绿色框匹配,如果下一个数T[i]与T[j]是匹配的,那么我们就很自然的更新Next[i+1] = j+1,两个数都向后匹配一个数
我们再看右边的情况
因为T[j] != T[i],因为j是前缀,肯定比i小,所以j = Next[j],j变到深绿色部分,看看T[j]与T[i]是否匹配,如果不匹配,还得往前回溯前缀,以此类推
斤斤计较的小z
import os
import sys
#求解next数组
#Next[i]存储T[0] - T[i-1]的最长公共前后缀
Next = [0] * 1000010
def get_next(t):
for i in range(1,len(t)):
j = Next[i]
#往前找匹配的j
while j > 0 and t[i] != t[j]:
j = Next[j]
if t[i] == t[j]:
Next[i+1] = j + 1
else:
Next[i+1] = 0
#返回s中t出现的次数
def KMP(s,t):
ans = 0
j = 0
for i in range(len(s)):
while j > 0 and s[i] != t[j]:
j = Next[j]
if s[i] == t[j]:
j += 1
if j == len(t):
ans += 1
j = Next[js
return ans
字典树
我们尽可能利用存在的边往下走,如果不存在,则构建新边
也可以对字典树中的每个结点打标记:遍历一次可以给结点的标记数加1,以此统计字符串的前缀数,如以a为前缀的字符串只有ab和aba,因此最开始遍历这两个字符串的时候,aa经过2号结点打一个标记,aba经过2号结点的时候又打了一个标记,以此维护了前缀a的个数
字典树实现的就是增删改查的功能
class TreeNode():
def __init__(self):
#表示当前节点往下走的后续节点
self.node = {}
#is_leaf是否为终止标志
self.is_leaf = False
def insert(self,s):
curr = self
#遍历字符串s
for c in s:
#判断下一步是否能走,不能则新增一个点
if c in curr.node.keys():
curr = curr.node[c]
else:
curr.node[c] = TreeNode()
curr = curr.node[c]
#字符串走完了,打上结束标志
curr.is_leaf = True
def prex(self,s):
curr = self
for c in s:
if c not in curr.node.keys():
return False
curr = curr.node[c]
return True
Tree = TreeNode()
并查集
模板
#并查集主要有两个操作:找根节点和合并
#设x的范围从1~n
#普通版本
def findroot(x):
while x != p[x]:
x = p[x]
return x
#路径压缩版本
def findroot(x):
if x == p[x]: return x
p[x] = findroot(p[x])
return p[x]
#合并
def Merge(x,y):
rootx,rooty = findroot(x),findroot(y)
p[rootx] = rooty
#父节点最开始都初始化为自身
p = list(range(n+1))
复盘
蒽…其实总的来说感觉本次Python不是很难,感觉稍微厉害一点的做个六道以上不是问题。
感觉前填空有点难
第一题:给的是一个很大个数的2*2正方形,和一个较小数的1 * 1正方形,问最大能拼成多长边长的正方形,其实这个我有一些思路,需要找一下规律,但是我当时就是先做后面的题了,最后没时间研究这个。
第二题:应该是判断特殊数字,给的那个例子非常大,需要找规律解决,暴力的话,你在四个小时一定跑不出来,我就没跑出来(悲)
后面六道都是提交代码题了
我记得中间四道都不是很难:但是其实背背模板还是有用的,中间有一道字符串匹配题,我忘了KMP咋写了,我就硬for循环匹配的,还有最小生成树Kruskal和并查集我都用上了
倒数第二道题我感觉就是搜索,但是他那个题我最开始没读明白判断条件要求是啥,改了好多次那个判断条件,我当时觉得能做出来这个,最后一个小时一直在死磕这个,结果那个电脑他比实际的时间慢3分钟,他结束了,我以为还有两分钟交代码,我就没交上(啊啊啊啊)
然后最后一道题也没做,我感觉我是有思路的,然后因为看错时间的缘故,前面两个填空题的答案也没交,蒽…反正第一次蓝桥杯之行就这样结束了,不想再回忆了,就随缘去吧,菜比的自救之路很漫长
但我体感Python赛道的难度还可以,大佬应该没有C++那边多,如果有uu们想比蓝桥,我还是很推荐python赛道的。