ruye07

导航

蓝桥杯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
素数函数用 for + math.sqrt 在大数时速度很慢,会被卡时间! 改进之后是
点击查看代码
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
还有一种优化方法 这里举筛选1000内素数的代码
点击查看代码
#筛出 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]

滑动窗口(双指针)

这种适用于全是正整数的数组

image
根据题意是找大于等于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 数组,相当于把和算了两遍,反而增加了空间开销,不会让代码更简便。

对于不全是正整数的数组

image


标准输入流读取(也常叫一次性读入所有输入)

import sys
#只是临时读数据,完全不动input
data = sys.stdin.read().split()

一次性把控制台 / 测试数据的所有内容,全部读成一整个字符串
速度比 input() 快 10~100 倍
专门用来解决 输入量巨大 的题目

多重嵌套压缩

image

点击查看代码
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)

约瑟夫问题

image

点击查看代码
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
得到
image

递归边界

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)

posted on 2026-04-29 23:05  ruye07  阅读(24)  评论(0)    收藏  举报