欢迎关注【Python·算法分类题库】,持续更新中……
知识点
A
字符串(AC自动机、拓展KMP、后缀数组、后缀自动机、回文自动机)
图论(网络流、一般图匹配)
数学(生成函数、莫比乌斯反演、快速傅里叶变换)
数据结构(树链剖分、二维/动态开点线段树、平衡树、可持久化数据结构、树套树、动态树)
B
排序(归并、快速、桶、堆、基数)
搜索(剪枝、双向BFS、记忆化搜索、迭代加深搜索、启发式搜索)
DP(背包、树形、状压、数位、常见优化)
字符串(哈希、KMP、字典树、Manacher)
图论(欧拉回路、最小生成树、单源最短路及差分约束系统、拓扑序列、二分图匹配、图的连通性问题[割点、桥、强连通分量]、DFS序、最近共同祖先)
数学(排列组合、二项式定理、容斥原理、模意义下的逆元、矩阵计算、高斯消元)
数据结构(ST表、堆[优先队列]、树状数组、线段树、Trie树、并查集、平衡树[利用系统自带的标准库实现简单平衡树])
计算几何(基础计算和基本位置关系判定、概率论、博弈论)
C
枚举,排序(冒泡、选择、插入)
搜索(DFS+BFS)
贪心
模拟
二分
DP(普通一维问题)
高精度
数据结构(栈、队列、链表)
数学(初等数论)
D
双指针,前缀和
分治与倍增〔倍增Floyd〕,位运算
三分,01分数规划
搜索
蒙特卡洛树搜索
字符串
最小表示法
数据结构
线性表,二叉树,集合,图的基本应用
并查集,二叉堆与树状数组
主席树,RMQ与LCA
DP
线性DP
数位DP,树形DP,计数DP,区间与环形DP,树与图上的DP
状态压缩DP,动态规划的设计与优化(单调队列DP)
数学
辗转相除法,进阶数论,组合数学与计数,概率与统计,基础线性代数,矩阵加速〔矩阵快速幂〕
散列函数,筛质数,gcd/lcm,快速幂,逆元,扩展欧几里得,费马小定理,更相减损术,博弈论,概率与统计,基础线性代数,矩阵加速(矩阵快速幂)
图论
树〔树的直径〕,最小生成树(Kruskal),连通性问题,分层图,类Floyd算法丨二分图,差分约束,连通分量,网络流,欧拉图
计算几何
点积,叉积,凸包
难度
无评定
入门
普及−
普及/提高−
普及+/提高
提高+/省选−
省选/NOI−
NOI/NOI+/CTSC
排序,快速幂
例题
[入门]
判断素数
def is_prime(number):
# 1 不是质数
if number == 1:
return False
# 只检查到根号number的整数,减少迭代次数
i = 2
while i * i <= number:
if number % i == 0:
return False
i += 1
return True
# 测试质数检查函数
print(is_prime(1)) # 预期输出: False
print(is_prime(2)) # 预期输出: True
print(is_prime(17)) # 预期输出: True
print(is_prime(18)) # 预期输出: False
print(is_prime(97)) # 预期输出: True
判断润年
def is_leap_year(year):
# 根据格里高利历法,年份是闰年的条件
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
# 测试闰年检查函数
print(is_leap_year(2000)) # 预期输出: True,因为2000能被400整除
print(is_leap_year(2004)) # 预期输出: True,因为2004能被4整除且不能被100整除
print(is_leap_year(2100)) # 预期输出: False,因为2100能被100整除但不能被400整除
print(is_leap_year(2017)) # 预期输出: False,因为2017不能被4整除
[普及-]
【模板】排序
将 N 个数从小到大排序后输出。
import sys
input = sys.stdin.read
data = input().split()
N = int(data[0]) # 读取N的值
a = list(map(int, data[1:N+1])) # 读取N个数并转换为整数列表
a.sort() # 对列表进行排序
print(" ".join(map(str, a))) # 将排序后的列表转换为字符串并输出
【模板】字符串哈希
给定 N N N 个字符串(第 i i i 个字符串长度为 M i M_i Mi,字符串内包含数字、大小写字母,大小写敏感),请求出 N N N 个字符串中共有多少个不同的字符串。
import sys
input = sys.stdin.read
# 读取输入数据
data = input().split()
N = int(data[0])
strings = data[1:N+1]
# 使用集合来自动去除重复的字符串
unique_strings = set(strings)
# 输出不同字符串的数量
print(len(unique_strings))
【模板】日期差
def is_leap_year(year):
# 根据格里高利历法,年份是闰年的条件
return (year % 4 == 0 and year % 100 != 0) or year % 400 == 0
def count_days(start_year, start_month, start_day, end_year, end_month, end_day):
"""
计算两个日期之间的天数差。
"""
days_count = 0
# 每个月的天数,注意:索引0的位置留空,以方便使用1-12月的索引。
days_in_month = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
while True:
if start_year == end_year and start_month == end_month and start_day == end_day:
break
start_day += 1
# 检查是否是闰年2月
if is_leap_year(start_year) and start_month == 2:
if start_day > days_in_month[start_month] + 1:
start_month += 1
start_day = 1
else:
if start_day > days_in_month[start_month]:
start_month += 1
start_day = 1
if start_month > 12:
start_month = 1
start_year += 1
days_count += 1
return days_count
# 应用该函数测试几个日期
print(count_days(2020, 1, 1, 2020, 1, 10)) # 从2020年1月1日到2020年1月10日,9天
print(count_days(2020, 2, 27, 2020, 3, 1)) # 跨越闰年2月,3天
【模板】快速幂||取余运算
计算 :2^10 mod 9=7
如:35,5化为二进制后是101,则35=1 * 320 * 1 * 322=1 * 3^1 * 1 * 3^4=243
def quick_pow_mod(a, b, p):
"""
快速幂算法计算 a^b mod p 的结果。
参数:
a (int): 基数。
b (int): 指数。
p (int): 模数。
返回:
int: 计算结果 a^b mod p。
"""
result = 1
base = a % p # 计算模 p 后的基数,避免在计算中数值过大
while b > 0:
if b % 2 == 1: # 如果 b 是奇数,取一个 base 加到结果中
result = (result * base) % p
base = (base * base) % p # 将 base 平方
b //= 2 # 将指数 b 除以 2
return result
# 读取输入值
a, b, p = map(int, input().split())
# 计算 a^b mod p
result = quick_pow_mod(a, b, p)
# 输出格式化的结果
print(f"{a}^{b} mod {p}={result}")
【模板】栈
import sys
input = sys.stdin.read
# 读取所有输入
data = input().split()
index = 0
T = int(data[index])
index += 1
# 进行T次测试
for _ in range(T):
stack = []
n = int(data[index])
index += 1
# 对每个测试用例处理n个操作
for _ in range(n):
command = data[index]
index += 1
if command == "push":
x = int(data[index])
index += 1
stack.append(x)
elif command == "pop":
if not stack:
print("Empty")
else:
stack.pop()
elif command == "query":
if not stack:
print("Anguei!")
else:
print(stack[-1])
else: # size command
print(len(stack))
【模板】队列
添加图片注释,不超过 140 字(可选)
from collections import deque
import sys
input = sys.stdin.read
data = input().split()
n = int(data[0]) # 读取操作数
queue = deque() # 创建双端队列
index = 1 # 初始化索引,用于跟踪输入数据
for _ in range(n):
op = int(data[index]) # 读取操作码
if op == 1:
x = int(data[index + 1]) # 如果操作是1,读取该操作的数值
queue.append(x) # 将数值加入队列
index += 2 # 更新索引,跳过已读取的数值
elif op == 2:
if not queue:
print(“ERR_CANNOT_POP”) # 如果队列为空,打印错误信息
else:
queue.popleft() # 如果不为空,从队列前端弹出元素
index += 1
elif op == 3:
if not queue:
print(“ERR_CANNOT_QUERY”) # 如果队列为空,打印错误信息
else:
print(queue[0]) # 如果不为空,打印队首元素
index += 1
else:
print(len(queue)) # 打印队列的当前大小
index += 1
【模板】堆(优先队列)
给定一个数列,初始为空,请支持下面三种操作:
- 给定一个整数 x x x,请将 x x x 加入到数列中。
- 输出数列中最小的数。
- 删除数列中最小的数(如果有多个数最小,只删除 1 1 1 个)。
import sys
import heapq
input = sys.stdin.read
# 读取输入数据
data = input().split()
n = int(data[0])
commands = data[1:]
# 创建一个小根堆来维护数列
heap = []
index = 0
results = []
# 遍历执行所有操作
for _ in range(n):
op = int(commands[index])
if op == 1:
# op=1,加入元素到堆中
x = int(commands[index + 1])
heapq.heappush(heap, x)
index += 2
elif op == 2:
# op=2,输出最小元素
results.append(str(heap[0]))
index += 1
elif op == 3:
# op=3,弹出最小元素
heapq.heappop(heap)
index += 1
# 打印所有需要输出的结果
print("\n".join(results))
【模板】埃氏筛
查询第 k k k 小的素数
import sys
import numpy as np
input = sys.stdin.read
def get_primes(n):
# 使用numpy创建位数组,初始化所有值为True
is_prime = np.ones(n + 1, dtype=bool)
is_prime[:2] = False # 0和1不是质数
p = 2
while (p * p <= n):
if is_prime[p]:
# 设置p的倍数为非质数
is_prime[p*p:n+1:p] = False
p += 1
# 使用numpy直接获取所有质数
primes = np.nonzero(is_prime)[0]
return primes
# 读取输入并处理
data = input().split()
n = int(data[0]) # 读取n
q = int(data[1]) # 读取查询次数q
primes = get_primes(n) # 获取所有质数
# 处理查询,映射到Python的0开始索引
query_indices = map(int, data[2:])
results = [primes[k-1] for k in query_indices] # 通过列表解析查询质数列表
# 输出结果
print('\n'.join(map(str, results)))
【模板】欧拉筛
通过维护一个素数列表,并对每个整数进行筛选,但与埃氏筛不同的是,它确保每个合数只被其最小的素因子筛选一次,这使得筛选的过程是线性的,即时间复杂度为O(n)。
def generate_primes(max_num):
"""
使用线性筛算法生成所有小于等于 max_num 的素数。
参数:
- max_num: int, 素数生成的上限。
返回:
- primes: list, 包含所有小于等于 max_num 的素数。
"""
# 初始化标记数组,初始为 False 表示所有数都未被访问
visited = [False] * (max_num + 1)
primes = []
# 从 2 开始遍历每一个数
for i in range(2, max_num + 1):
# 如果当前数 i 未被标记,说明它是素数
if not visited[i]:
primes.append(i)
# 使用当前找到的素数去标记其倍数
for prime in primes:
# 计算当前素数的倍数
composite = i * prime
# 如果超出范围,停止标记
if composite > max_num:
break
# 标记合数
visited[composite] = True
# 如果 i 能被 prime 整除,停止标记当前 i 的更高倍数
if i % prime == 0:
break
return primes
# 应用函数并打印结果
max_number = 100 # 示例中设置 MAXN 为 100
prime_numbers = generate_primes(max_number)
print("Primes up to", max_number, ":", prime_numbers)
[普及/提高−]
【模板】双指针
P1638 逛画展
博览馆正在展出由世上最佳的 𝑚 位画家所画的图画。
游客在购买门票时必须说明两个数字,𝑎 和 𝑏,代表他要看展览中的第 𝑎 幅至第 𝑏 幅画(包含 𝑎,𝑏)之间的所有图画,而门票的价钱就是一张图画一元。
Sept 希望入场后可以看到所有名师的图画。当然,他想最小化购买门票的价格。
请求出他购买门票时应选择的 𝑎,𝑏 数据保证一定有解。
若存在多组解,输出 𝑎 最小的那组。
def find_minimum_segment(n, m, painters):
# 初始化计数器
counts = [0] * (m + 1)
unique = 0
min_length = float('inf')
result = (-1, -1)
left = 0
# 使用滑动窗口法
for right in range(n):
# 记录当前画家编号
painter = painters[right]
# 如果这个画家之前计数为0,那么我们有了一个新的唯一画家
if counts[painter] == 0:
unique += 1
counts[painter] += 1
# 当我们拥有全部所需的画家时
while unique == m:
# 检查是否是更小的窗口
current_length = right - left + 1
if current_length < min_length:
min_length = current_length
result = (left + 1, right + 1) # 转为 1-based index
# 尝试移动左指针来缩小窗口
counts[painters[left]] -= 1
if counts[painters[left]] == 0:
unique -= 1
left += 1
return result
# 读入数据
import sys
input = sys.stdin.read
data = input().split()
n, m = int(data[0]), int(data[1])
painters = list(map(int, data[2:]))
# 获取最小区间
a, b = find_minimum_segment(n, m, painters)
print(a, b)
【模板】并查集
题目描述
现有一个并查集,你需要完成合并和查询操作。
输入格式
第一行包含两个整数
N
,
M
N,M
N,M ,表示共有
N
N
N 个元素和
M
M
M 个操作。
接下来
M
M
M 行,每行包含三个整数
Z
i
,
X
i
,
Y
i
Z_i,X_i,Y_i
Zi,Xi,Yi 。
当
Z
i
=
1
Z_i=1
Zi=1 时,将
X
i
X_i
Xi 与
Y
i
Y_i
Yi 所在的集合合并。
当
Z
i
=
2
Z_i=2
Zi=2 时,输出
X
i
X_i
Xi 与
Y
i
Y_i
Yi 是否在同一集合内,是的输出
Y
;否则输出 N
。
输出格式
对于每一个
Z
i
=
2
Z_i=2
Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y
或者 N
。
import sys
input = sys.stdin.read
class UnionFind:
def __init__(self, size):
# 初始化:每个节点的父节点指向自己,秩(树的高度)为1
self.parent = [i for i in range(size)]
self.rank = [1] * size
def find(self, p):
# 查找根节点,并进行路径压缩
if self.parent[p] != p:
self.parent[p] = self.find(self.parent[p]) # 路径压缩
return self.parent[p]
def union(self, p, q):
# 将两个节点所在的集合合并,按秩合并
rootP = self.find(p)
rootQ = self.find(q)
if rootP != rootQ:
if self.rank[rootP] > self.rank[rootQ]:
self.parent[rootQ] = rootP # 较小的树合并到较大的树
elif self.rank[rootP] < self.rank[rootQ]:
self.parent[rootP] = rootQ
else:
self.parent[rootQ] = rootP
self.rank[rootP] += 1 # 如果相等,选择一个作为根,并增加秩
# 读取输入数据
data = input().split()
index = 0
N = int(data[index])
index += 1
M = int(data[index])
index += 1
uf = UnionFind(N + 1) # 创建并查集实例,节点编号从1开始到N
output = []
for _ in range(M):
Z = int(data[index])
index += 1
X = int(data[index])
index += 1
Y = int(data[index])
index += 1
if Z == 1:
uf.union(X, Y) # 合并操作
elif Z == 2:
# 查询两个节点是否在同一集合
if uf.find(X) == uf.find(Y):
output.append('Y')
else:
output.append('N')
# 输出查询结果
print('\n'.join(output))
【模板】最短路(Dijkstra)
正权图请使用dijkstra算法,负权图请使用SPFA算法。
如果图是稀疏的,使用链式前向星更加高效;如果图是稠密的,使用邻接表更加高效。
给定一个
n
n
n 个点,
m
m
m 条有向边的带非负权图,请你计算从
s
s
s 出发,到每个点的距离。(单源最短路径)
数据保证你能从
s
s
s 出发到任意点。
输入格式
第一行为三个正整数
n
,
m
,
s
n, m, s
n,m,s。
第二行起
m
m
m 行,每行三个非负整数
u
i
,
v
i
,
w
i
u_i, v_i, w_i
ui,vi,wi,表示从
u
i
u_i
ui 到
v
i
v_i
vi 有一条权值为
w
i
w_i
wi 的有向边。
输出格式
输出一行
n
n
n 个空格分隔的非负整数,表示
s
s
s 到每个点的距离。
from sys import stdin
from heapq import heappush, heappop
# 读取节点数、边数和起始节点
n, m, s = map(int, input().split())
# 构建图
graph = {i: {} for i in range(1, n + 1)}
for _ in range(m):
u, v, w = map(int, stdin.readline().split())
# 确保存储的是两节点间的最小权重
if v in graph[u]:
graph[u][v] = min(graph[u][v], w)
else:
graph[u][v] = w
# Dijkstra 算法实现
def dijkstra(graph, start):
visit = [False] * (n + 1) # 标记已经访问过的节点
d = [(1 << 31) - 1] * (n + 1) # 存储起点到每个点的最短路径长度,使用 2^31 - 1 表示无限大
d[start] = 0
heap = [(0, start)] # 优先队列
while heap:
dist, u = heappop(heap)
if visit[u]:
continue
visit[u] = True
for v, weight in graph[u].items():
if d[v] > d[u] + weight:
d[v] = d[u] + weight
heappush(heap, (d[v], v))
return d
# 获取最短路径列表并输出
distances = dijkstra(graph, s)
# 输出从起点到每个点的最短路径,从 1 到 n,不包括起始点的位置
print(*distances[1:])
【模板】最小生成树(Kruskal)
给出一个无向图,求出最小生成树,如果该图不连通,则输出 orz
。
输入格式
第一行包含两个整数
N
,
M
N,M
N,M,表示该图共有
N
N
N 个结点和
M
M
M 条无向边。
接下来
M
M
M 行每行包含三个整数
X
i
,
Y
i
,
Z
i
X_i,Y_i,Z_i
Xi,Yi,Zi,表示有一条长度为
Z
i
Z_i
Zi 的无向边连接结点
X
i
,
Y
i
X_i,Y_i
Xi,Yi。
输出格式
如果该图连通,则输出一个整数表示最小生成树的各边的长度之和。如果该图不连通则输出 orz
。
import sys
input = sys.stdin.read
from heapq import heappop, heappush
class UnionFind:
def __init__(self, size):
self.parent = list(range(size)) # 初始化每个节点的父节点指向自己
self.rank = [0] * size # 初始化秩,用于优化合并过程
def find(self, p):
if self.parent[p] != p:
self.parent[p] = self.find(self.parent[p]) # 路径压缩优化
return self.parent[p]
def union(self, p, q):
rootP = self.find(p)
rootQ = self.find(q)
if rootP != rootQ:
if self.rank[rootP] > self.rank[rootQ]:
self.parent[rootQ] = rootP # 根据秩进行合并,保持树的平衡
elif self.rank[rootP] < self.rank[rootQ]:
self.parent[rootP] = rootQ
else:
self.parent[rootQ] = rootP
self.rank[rootP] += 1 # 如果秩相同,则合并后秩增加
return True
return False
def kruskal(n, edges):
uf = UnionFind(n)
mst_cost = 0
mst_edges = 0
while edges and mst_edges < n - 1:
cost, u, v = heappop(edges) # 从优先队列中取出最小权重的边
if uf.union(u, v): # 如果边连接的两个节点不在同一连通分量中,则加入MST
mst_cost += cost
mst_edges += 1
if mst_edges == n - 1: # 如果MST的边数等于节点数减一,返回总成本
return mst_cost
else:
return 'orz' # 如果无法形成MST,返回'orz'
def main():
data = input().split()
n = int(data[0]) # 节点数量
m = int(data[1]) # 边的数量
edges = []
index = 2
for _ in range(m):
x = int(data[index]) - 1 # 节点编号从0开始
y = int(data[index+1]) - 1
z = int(data[index+2])
index += 3
if x != y: # 避免加入自环
heappush(edges, (z, x, y)) # 将边加入最小堆
result = kruskal(n, edges) # 调用Kruskal算法函数
print(result)
main()
[普及+/提高]
【模板】KMP||字符串匹配
题目描述
给出两个字符串
s
1
s_1
s1 和
s
2
s_2
s2,若
s
1
s_1
s1 的区间
[
l
,
r
]
[l, r]
[l,r] 子串与
s
2
s_2
s2 完全相同,则称
s
2
s_2
s2 在
s
1
s_1
s1 中出现了,其出现位置为
l
l
l。
现在请你求出
s
2
s_2
s2 在
s
1
s_1
s1 中所有出现的位置。
定义一个字符串
s
s
s 的 border 为
s
s
s 的一个非
s
s
s 本身的子串
t
t
t,满足
t
t
t 既是
s
s
s 的前缀,又是
s
s
s 的后缀。
对于
s
2
s_2
s2,你还需要求出对于其每个前缀
s
′
s'
s′ 的最长 border
t
′
t'
t′ 的长度。
输入格式
第一行为一个字符串,即为
s
1
s_1
s1。
第二行为一个字符串,即为
s
2
s_2
s2。
输出格式
首先输出若干行,每行一个整数,按从小到大的顺序输出
s
2
s_2
s2 在
s
1
s_1
s1 中出现的位置。
最后一行输出
∣
s
2
∣
|s_2|
∣s2∣ 个整数,第
i
i
i 个整数表示
s
2
s_2
s2 的长度为
i
i
i 的前缀的最长 border 长度。
def compute_prefix_function(pattern):
"""计算KMP算法中的部分匹配表(前缀函数)"""
m = len(pattern)
pi = [0] * m
k = 0
for q in range(1, m):
while k > 0 and pattern[k] != pattern[q]:
k = pi[k - 1]
if pattern[k] == pattern[q]:
k += 1
pi[q] = k
return pi
def kmp_search(text, pattern):
"""使用KMP算法搜索pattern在text中所有出现的起始位置"""
n = len(text)
m = len(pattern)
pi = compute_prefix_function(pattern)
q = 0 # 匹配长度
positions = [] # 记录匹配的开始位置
for i in range(n):
while q > 0 and pattern[q] != text[i]:
q = pi[q - 1]
if pattern[q] == text[i]:
q += 1
if q == m:
positions.append(i - m + 1)
q = pi[q - 1]
return positions
def compute_border_lengths(pattern):
"""计算每个前缀的最长border长度"""
m = len(pattern)
pi = compute_prefix_function(pattern)
return pi
# 读入数据
import sys
input = sys.stdin.read
data = input().split()
s1 = data[0]
s2 = data[1]
# 计算s2在s1中的所有出现位置
positions = kmp_search(s1, s2)
for pos in positions:
print(pos + 1) # 输出位置,从1开始计数
# 计算s2的每个前缀的最长border长度
border_lengths = compute_border_lengths(s2)
print(' '.join(map(str, border_lengths)))
【模板】矩阵快速幂
仅55points,待优化。
题目描述
给定
n
×
n
n\times n
n×n 的矩阵
A
A
A,求
A
k
A^k
Ak。
输入格式
第一行两个整数
n
,
k
n,k
n,k。
接下来
n
n
n 行,每行
n
n
n 个整数,第
i
i
i 行的第
j
j
j 的数表示
A
i
,
j
A_{i,j}
Ai,j。
输出格式
输出
A
k
A^k
Ak
共
n
n
n 行,每行
n
n
n 个数,第
i
i
i 行第
j
j
j 个数表示
(
A
k
)
i
,
j
(A^k)_{i,j}
(Ak)i,j,每个元素对
1
0
9
+
7
10^9+7
109+7 取模。
import sys
input = sys.stdin.read
MOD = 1000000007
def matrix_mult(A, B, size):
"""优化的矩阵乘法实现,减少了函数调用,并尝试内联一些操作。"""
result = [[0] * size for _ in range(size)]
for i in range(size):
result_row = result[i]
for j in range(size):
sum = 0
A_row = A[i]
for k in range(size):
sum += A_row[k] * B[k][j]
result_row[j] = sum % MOD
return result
def matrix_power(matrix, size, power):
"""使用快速幂算法优化矩阵幂运算。"""
result = [[1 if i == j else 0 for j in range(size)] for i in range(size)]
base = matrix
while power > 0:
if power % 2 == 1:
result = matrix_mult(result, base, size)
base = matrix_mult(base, base, size)
power >>= 1
return result
# 读取和解析输入数据
data = input().split()
n = int(data[0])
k = int(data[1])
index = 2
A = []
for i in range(n):
row = []
for j in range(n):
row.append(int(data[index]))
index += 1
A.append(row)
# 特殊情况处理:k为0时输出单位矩阵
if k == 0:
for i in range(n):
print(' '.join('1' if i == j else '0' for j in range(n)))
else:
result = matrix_power(A, n, k)
for row in result:
print(' '.join(str(x) for x in row))