蓝桥杯python国赛学习笔记记录(持续更新)
四舍五入和进位
math.ceil()遇小数则进一
round四舍五入
旋转矩阵
如将
a=[[1,2,3],
[4,5,6],
[7,8,9]]
顺时针旋转90°,用b = list(zip(*a[::-1]))
逆时针旋转90°则用b = list(zip(*a))[::-1]
高精度+快速幂
例题:求2^p的长度及后500位数(不足向前补0)(p最大可达3010000)
暴力的解法是
import math
n=
int(input())
num=2**n-1
l=math.ceil(n*math.log10(2))
print(l)
if l>500:
print(str(num[-500:])
else:
print('{:0>500}'.format(num))
log10(2^p) = p × log10(2)
然而计算量太大从而计算不出来
2^p 的位数 = p * log10(2) + 1
对于大指数数字求位数不用直接求数字,直接用公式
对于求后n位(如后500位的),只要保留后n位进行计算即可
对此可以建立个数组
通过40%测试的解法是q
import math
n=int(input())
l=math.ceil(n*math.log10(2))
print(l)
ans=[0]*500
ans[0]=1
for i in range(n): #乘n次2
jin=0
for j in range(500):
total=ans[j]*2+jin
ans[j]=total%10
jin=total//10
ans[0]-=1 #ans[0]不可能为0,故不考虑负数
cnt=0
for w in range(499,-1,-1):
print(ans[w],end='')
cnt+=1
if not cnt%50:
print()
#这才跑出40%的样例。。。。。
只能找个更牛逼的方法
那就是把 “一次次乘 2” 变成 “不断平方”,把 310 万次循环降到 20 次!
import math
p = int(input())
# 计算位数
print(int(p * math.log10(2)) + 1)
# 高精度数组,只存最后500位
ans = [0] * 500
base = [0] * 500
ans[0] = 1
base[0] = 2
# 高精度乘法(两个高位a * b,保留最后500位)
def mul(a, b):
tmp = [0] * 500
for i in range(500):
if a[i] == 0:
continue
carry = 0
for j in range(500 - i):
tmp[i+j] += a[i] * b[j] + carry
carry = tmp[i+j] // 10
tmp[i+j] %= 10
return tmp
# 快速幂模板
while p > 0:
if p % 2 == 1:
ans = mul(ans, base)
base = mul(base, base)
p = p // 2
# 2^p - 1
ans[0] -= 1
# 输出 50位一行
cnt = 0
for i in range(499, -1, -1):
print(ans[i], end="")
cnt += 1
if cnt % 50 == 0:
print()
#快速幂模板(不取模版)
def fast_pow(a, n):
"""计算 a^n,n 为非负整数"""
result = 1
base = a
while n > 0:
if n & 1: # 若当前二进制位为1
result *= base
base *= base # 底数平方
n >>= 1 # 右移一位
return result
素数判断的优化
写题的时候碰到的
我定义的函数是
点击查看代码
def is_prime(num):
if num < 2:
return False
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True
点击查看代码
def is_prime(num):
if num < 2:
return False
if num == 2:
return True
if num % 2 == 0:
return False
i = 3
while i * i <= num: # i<math.sqrt(num)
if num % i == 0:
return False
i += 2
return True
点击查看代码
#筛出 1~1000 所有素数
max_n = 1000
is_prime = [True] * (max_n + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, max_n + 1):
if is_prime[i]:
for j in range(i*i, max_n+1, i):
is_prime[j] = False
primes = [i for i, val in enumerate(is_prime) if val]
滑动窗口(双指针)
这种适用于全是正整数的数组

根据题意是找大于等于target的连续数组,可以用快慢指针
class Solution:
def minarrlen(self,target:int,nums:List[int]):
l=0
length=float('inf')
n=len(nums)
sums=0
for r in range(n): #l,r表示左,右
sums+=nums[r]
while sums>=target: #使滑动窗口尽可能小
length=min(length,r-l+1) #记录最小长度
sums-=nums[l]
l+=1
return length if length != float('inf') else 0
突然想到前缀和,问了ai的回答是:滑动窗口本身就是通过 current_sum += num 和 current_sum -= nums[left] 动态维护窗口和,本质上就是在 “边走边算前缀和”。如果再额外开一个 prefix 数组,相当于把和算了两遍,反而增加了空间开销,不会让代码更简便。
对于不全是正整数的数组

标准输入流读取(也常叫一次性读入所有输入)
import sys
#只是临时读数据,完全不动input
data = sys.stdin.read().split()
一次性把控制台 / 测试数据的所有内容,全部读成一整个字符串
速度比 input() 快 10~100 倍
专门用来解决 输入量巨大 的题目
多重嵌套压缩

点击查看代码
s = input().strip()
idx = 0
def dfs():
global idx
res = ''# 表示压缩的字符串,且完全独立便于递归
while idx < len(s):
c = s[idx]
if c == '[':
idx += 1 # 跳过 '['
# 读取数字
num = 0
while idx < len(s) and s[idx].isdigit():
num = num * 10 + int(s[idx])
idx += 1
# 递归解析内部子串
inner = dfs() # 这里会处理直到匹配的 ']'
res += inner * num
elif c == ']':
idx += 1 # 跳过 ']'
return res
else: # 普通字符
res += c
idx += 1
return res
#dfs()的对象是第一对的[]
print(dfs())
八皇后(搜索树+可行解)
P1219 [USACO1.5] 八皇后 Checker Challenge
题目描述
一个如下的 $6 \times 6$ 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 $2\ 4\ 6\ 1\ 3\ 5$ 来描述,第 $i$ 个数字表示在第 $i$ 行的相应位置有一个棋子,如下:
行号 $1\ 2\ 3\ 4\ 5\ 6$
列号 $2\ 4\ 6\ 1\ 3\ 5$
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 $3$ 个解。最后一行是解的总个数。
输入格式
一行一个正整数 $n$,表示棋盘是 $n \times n$ 大小的。
输出格式
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
输入输出样例 #1
输入 #1
6
输出 #1
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
说明/提示
【数据范围】
对于 $100%$ 的数据,$6 \le n \le 13$。
题目翻译来自NOCOW。
USACO Training Section 1.5
点击查看代码
import sys
sys.setrecursionlimit(1000000)#扩栈
n=int(input())
cnt=0
ans=[]
lie=[0]*(n+1) #列
zhu=[0]*(2*n+1) #主对角线
fu=[0]*(2*n+1) #副对角线
def dfs(row):
global cnt
if row>n:
cnt+=1
if cnt<=3:
print(' '.join(map(str,ans)))
return
for c in range(1,n+1):
#对角线公式
d1=row-c+n #主
d2=row+c #副
if not lie[c] and not zhu[d1] and not fu[d2]:
lie[c]=1
zhu[d1]=1
fu[d2]=1
ans.append(c)
dfs(row+1)#递归下一层
ans.pop()#对本层恢复
lie[c]=0
zhu[d1]=0
fu[d2]=0
dfs(1)
print(cnt)
约瑟夫问题

点击查看代码
n,m=map(int,input().split())
p=list(range(1,n+1))
ptr=0
out=[]
while p: #设计一个指针指向人
ptr=(ptr+m-1)%len(p) #n人数会变化,让指针归零
k=p.pop(ptr)
out.append(k)
print(' '.join(list(map(str,out))))
pop 之后,后面的元素会向前移动,索引会改变。这正是约瑟夫问题算法巧妙的地方——指针不需要额外移动,因为列表收缩后,原 ptr 已经自动指向了下一轮起始的人。
拓展欧几里得算法(exgcd)
普通欧几里得:
gcd(a,b)=gcd(b,amodb)
裴蜀定理:
设 g=gcd(a,b),一定存在整数 x,y使得ax+by=g
拓展欧几里得算法就是求出g,x,y来的
def exgcd(a, b):
# 返回 g, x, y 满足 ax + by = gcd(a,b)
if b == 0:
return a, 1, 0
g, x1, y1 = exgcd(b, a % b)
q = ⌊ a / b ⌋
x = y1 //x递推
y = x1 - q * y1 //y递推
return g, x, y
核心公式推导
amodb=a-qb
设对(b,amodb)已经求出解
b·x1+(amodb)·y1=g
b·x1+(a-qb)·y1=g
a·y1+b·(x1-qy1)=g
对比ax+by=g
得到

递归边界
gcd(a,0)=a
故边界时x=1,y=0
并查集的优化
find函数
我之前省赛的find函数这么写的,便于理解
def find(x):
if parent[x]==x:
return x
else:
return find(parent[x])
优化: 如果树退化成一条链,你的 find 每次要爬 O(n) 层。怎么剪短路径?
提示:你在 else 分支返回之前,多赋值一步——把当前节点的父亲直接指向根,下次查询就 O(1) 了。
def find(x):
if parent[x] == x:
return x
parent[x] = find(parent[x]) # 路径压缩
return parent[x]
union函数
把 x 所在集合的根,指向 y 所在集合的根。用 find 来写,两行就够。
def union(x,y):
parent[find(x)]=find(y)
浙公网安备 33010602011771号