基础算法

排序

快速排序 (双指针)

  • 确定分界点(基准数的选择)

    首,尾,中间值,随机值

  • 调整区间

    如何优美的分成两块

    i,j 停止移动后判断

    互相不穿过,交换之后,i,j 各自往中间移动一位

    当i,j 互相穿过之后,不交换数值 这个时候分块,分成两份递归处理

    i(任意时刻的在i指针左边的值都是小于基准值的),j(同理,都大于) 作为指针,从左右端依次开始移动,当i 移动到不小于的时候停止移动,j 看开始移动,当j 移动到不大于的时候停止移动。

  • 递归处理左右两边

代码

def quick_sort(arr, l, r):
pivot = arr[l]
i,j = l-1,r+1
"""
这里指针指向两侧偏移一个位置的原因
后续当交换元素时候将 i,j 移动到下一个位置
whatever 先将i ,j 移动到下一个位置再去判断。
因此这里需要提前偏移
"""
if l >= r: return
while True:

while True :
i+=1
if arr[i] > pivot:break

while True:
j-=1
if arr[j] < pivot:break

if i >= j: break
#外层循环当i>=j时候退出
arr[i],arr[j] = arr[j],arr[i]

"""
分成这样的三个区域
[l ... j] [pivot] [j+1 ... r]
基准值用的是最左边的值arr[l]
j 往右移动,最后停在了一个< pivoit 的位置
这保证了j位置的元素一定是小于等于pivot的
"""

quick_sort(arr,l,j)
quick_sort(arr,j+1,r)
return arr


arr = list(map(int,input().split()))
quick_sort(arr,0,len(arr)-1)
print(arr)

归并排序

  • 确定分块点 mid = (l+r)//2
  • 递归排序左右两部分l,r
  • 归并——合二为一

代码


def merge_sort(arr,l,r):
res = []
if l>=r:
return
mid = (l+r)//2
merge_sort(arr,l,mid)
merge_sort(arr,mid+1,r)
i,j = l,mid+1
while i<=mid and j<=r: #因为是先添加元素再移动指针,所以这里是<=,结束条件是一方到达终点(终点出也要判断)
if arr[i] <= arr[j]:
res.append(arr[i])
i+=1 #这里要先添加元素,再移动指针
else:
res.append(arr[j])
j+=1

if i <= mid:
res.extend(arr[i:mid+1])
if j <= r:
res.extend(arr[j:r+1])
arr = [x for x in res]
return arr

arr = list(map(int,input().split()))
print(merge_sort(arr,0,len(arr)-1))

时间复杂度都是 nlogn

二分

整数二分

#模板  
while l < r :
mid = (l+r+1)//2
if (arr[mid]):
l = mid
else:
r = mid - 1
while l < r :
mid = (l+r)//2
if (arr[mid]):
r = mid
else:
l = mid+1

最终退出循环的时候,返回 l 或者r 都是正确的

浮点数二分

不需要考虑边界,也不需要加+=1

while
mid =
if
r =mid
else:
l = mid

高精度

前缀和差分

快速求出数组的前n项和

前缀和

一维前缀和

  • 主前缀和函数(prefix_sum)

    前缀和 数组长度为n+1

    s[i] = a[i-1] + s[i-1] (初始设s[0] = 0)

  • 区间和

    下标偏移 [r]-[l-1]

    python 数组索引从0 开始

在python 中可以使用前缀和工具 prefix_sum = list(accumulate(arr,initial = 0)) 直接计算前缀和 from itertools import accumulate


#前缀和
"""输入一个长度为n的整数序列,接下来输入m个询问
每个询问输入一堆l,r
对于每个询问,输出原序列中从第l个数到第r个数的和
"""

def pre_sum_query(arr):
#计算前缀和
"""
Python 中数组下标从0开始
b[i] = b[i-1]+a[i-1]
"""
n = len(arr)
prefix_sum =[0]*(n+1)
for i in range(1,n+1):
prefix_sum[i] = prefix_sum[i-1]+arr[i-1]
return prefix_sum


def prefix_sum(prefix_sum,l,r):
return prefix_sum[r+1]-prefix_sum[l]


n,m = map(int,input().split()) #解包
arr = list(map(int,input().split()))

prefix_sum = pre_sum_query(arr)

for _ in range(m):
l,r = map(int,input().split())
print(prefix_sum(prefix_sum,l,r))



二维前缀和

  • 前缀和函数

    prefix[i] [j] = prefix[i-1] [j] + prefix[i] [j-1] - prefix[i-1] [j-1]+a[i-1][j-1]

  • 任意子矩阵和(x1,y1,,x2,y2)

    区域和 = prefix[x2] [y2] -prefix[x1-1] [y2] -prefix[x2] [y1-1] + prefix[x1-1] [y1-1]


#二维前缀和
"""输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1,y1,x2,y2
表示一个子矩阵的左上角坐标和右下角坐标"""
def pre_prefix_sum(arr):
n,m = len(arr),len(arr[0])
prefix_num = [[0]*(m+1) for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,m+1):
prefix_num[i][j] = prefix_num[j-1][i]+prefix_num[i][j-1]-prefix_num[i-1][j-1]+arr[i-1][j-1]
return prefix_num

def query_prefix(prefix_num,x1,y1,x2,y2):
return prefix_num[x2][y2]-prefix_num[x2][y1-1]-prefix_num[x1-1][y2]+prefix_num[x1-1][y1-1]

n,m,p = map(int,input().split())
arr = [[int(x) for x in input().split()] for _ in range(n)]

prefix = pre_prefix_sum(arr)
for _ in range(p):
x1,y1,x2,y2 = map(int,input().split())
print(query_prefix(prefix,x1,y1,x2,y2))



差分

一维差分

  • 差分数组的构建

    diff 用来储存差分值 = nums[i]-nums[i-1]

    diff[i]+=diff[i-1] 是将差分数组转化为前缀和

    如果只有一次操作,diff[i] += diff[i-1] 最终返回diff 就是新的nums 的值

  • 累计增量到原数组

    nums[i-1]+=diff[i]

  • 数学推导

    diff[l]+=c

    diff[r+1]-=c

    计算差分数组的前缀和

    >for i in range(1,n+1):
    diff[i]+=diff[i-1]
    nums[i-1]+=diff[i] #累计

代码


def apply_range_add(n,nums,operations):
diff = [0]*(n+2)
for l,r,c in operations:
diff[l]+=c
dif[r+1]-=c
for i in range(1,n+1):
diff[i]+=diff[i-1]
nums[i-1]+=diff[i]

return nums
n,m = map(int,input().split())
nums = list(map(int,input().split()))
operations = [tuple(map(int,input().split())) for _ in range(m)]
result = apply_range_add(n,nums,operations) #返回结果
print(" ".join(map(str,result)))

二维数组

  • 差分数组的构建

    diff 用来储存差分值

    diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1] 是将差分数组转化为前缀和

  • 累计增量到原数组

    grid[i-1][j-1] +=diff[i][j]

  • 数学推导

    diff[x1][y1]+=c

    diff[x2+1][y1]-=c

    diff[x1][y2+1]-=c

    diff[x2+1][y2+1]+=c

    计算差分数组的前缀和

    >for i in range(1,n+1):
    diff[i][j]+=diff[i-1][j]+diff[i][j-1]-diff[i-1][j-1]
    grid[i-1][j-1]+=diff[i][j]#累计

代码


def apply_range_add_2d(n, m, grid, operations):

# 构建二维差分数组
diff = [[0] * (m + 2) for _ in range(n + 2)]

# 记录每个操作对差分数组的贡献
for x1, y1, x2, y2, c in operations:
diff[x1][y1] += c
diff[x2 + 1][y1] -= c
diff[x1][y2 + 1] -= c
diff[x2 + 1][y2 + 1] += c

# 通过二维差分还原整个矩阵
for i in range(1, n + 1):
for j in range(1, m + 1):
diff[i][j] += diff[i - 1][j] + diff[i][j - 1] - diff[i - 1][j - 1]
grid[i - 1][j - 1] += diff[i][j] # grid是0-based

return grid


n, m, p = map(int, input().split())
grid = [list(map(int, input().split())) for _ in range(n)]
operations = [tuple(map(int, input().split())) for _ in range(p)]

result = apply_range_add_2d(n, m, grid, operations)
for row in result:
print(" ".join(map(str, row)))

双指针算法

分为两大类: 指针指向两个序列(归并排序);指针指向一个序列(对撞指针,快慢指针,滑动窗口)

伪代码模板

j = 0
while i //for i in range
while j<=i and check(i,j):
j+=1

核心思想:将两个for 循环的复杂度变成o(n)

滑动窗口

最小/最大区间,最长子串等问题

核心思想: i 在移动的时候,寻找j 的值,可以确定的是,两者是单调的关系,即 i 往右移动的时候,j 也一定最终往右边移动的

  • 最长无重复子序列:一般需要用哈希表

    def longest_subsequence(nums):
    i,j = 0,0
    n = len(nums)
    max_length = 0
    hashmap = {}
    for i in range(n):
    while j <= i and nums[i] in hashmap:
    j = hashmap[[nums[i]]+1
    hashmap[nums[i]] = i
    max_length = max(max_length,i-j+1)
    return max_length


  • 和不超过k 的最长子序列

    def max_length_k(nums,k):
    n = len(nums)
    i,j = 0,0
    sumn = 0
    max_l = 0
    for i in range(n):
    sumn+=nums[i]
    while j<= i and sumn > k:
    sumn -= nums[j]
    j+=1
    max_l = max()
    return max_l

  • 和大于等于k 的最短子数组

    def ():
    min_length = floar("inf")
    for :

    while sumn >=k:
    min_length = #满足条件就更新
    sumn -=
    l-=1
    return

    关键思想:

    • while 条件决定何时缩小窗口
    • 更新长度(max_lengthmin_length)必须正确放置在 while 循环内或外
    • 更新长度的时机应该与 while 条件的逻辑保持一致,但具体的位置取决于这是最长还是最短子数组问题。

快慢指针

单链表,数组,用于寻找环,去重

  • 删除重复元素

    def delete_repeat(arr):
    slow,fast = 0,0
    while fast < len(arr):
    if arr[slow] != arr[fast]:
    slow+=1
    arr[slow] = arr[fast] #这两个更新顺序不能变
    fast+=1
    return slow+1

对撞指针

用于已排列(可排序)的数组的区间问题

位运算

直接一道例题

"""给定一个长度为n的数列,求二进制表示中1的个数
第一行包含整数n ,第二行包含n个整数,表示整个数列"""
def lowbit(x):
return x&-x
def count_ones(nums):
ans = []
for num in nums:
count = 0
while num:
num -= lowbit(num) #num 每次都减去最后一个1
count+=1
ans.append(count)
return ans
n = int(input())
nums = list(map(int,input().split()))
print(" ".join(map(str,count_ones(nums))))

离散化

特指整数的有序(排序)的离散化

  • 数组中arr[] 中可能有重复的值——> 去重

    unique = list(set(arr))
  • 如何算出arr[i] 离散化之后的值 ——>二分求插入位置

    def ():
    i,j = 0,len(arr)-1
    while l<r:
    mid = l+r//2
    if mid[i] >= x:
    r =mid
    else:
    l = mid+1
    return r(l) + 1 #加1 是让其下标从1开始,加不加一看题目要求

第一种方法

import bisect
def derect(arr):
ans = []
b = list(set(arr))
b.sort() #将b 数组离散化
for num in arr:
ans.append(bisect.bisect_left(b,num)) #求在离散化数组中的位置
return ans

第二种方法

def derect(arr):
ans = []
b = list(set(arr))
b.sort()
value = list(range(len(b)))
dic = dict(zip(b,value))
for num in arr:
ans.append(dic[x])

例题


#离散化(映射)
"""假设有一个无限长的数轴,数轴上每个数都是0
现在我们进行n次操作,每次操作将某一位置x上的数加c
接下来,进行m次询问,每次询问包含两个整数l,r,求区间[l,r]中所有数的和 ——> 差分+前缀和?
第一行包含两个整数n,m
接下来n行,每行包含两个整数x,c
接下来m行,每行包含两个整数l,r

输出: 输出m个整数,表示每个询问的答案
"""
import bisect
n, m = map(int, input().split())
# 统计每个位置的总增加值
d = {}
for _ in range(n):
x, c = map(int, input().split())
if x not in d:
d[x] = c
else:
d[x] += c

# 离散化:去重并排序
alls = sorted(d.keys())

# 构建前缀和数组
pre_sum = [0] * (len(alls) + 1)
for i in range(len(alls)):
pre_sum[i+1] = pre_sum[i] + d[alls[i]]

# 处理每个查询
for _ in range(m):
l, r = map(int, input().split())
# 找到左边界(第一个 >= l 的位置)
left = bisect.bisect_left(alls, l)
# 找到右边界(最后一个 <= r 的位置)
right = bisect.bisect_right(alls, r) - 1
if left > right:
print(0)
else:
print(pre_sum[right + 1] - pre_sum[left])

区间合并

  • 将所给区间,按照左端点为基准排序
  • 维护一个当前值 st,ed 循环时候更新


def merge_intervals(intervals):
if not intervals:
return 0
intervals.sort(key = lambda x : x[0] ) #X代表每一个元素 : X[0] 取左端点为排序依据
count = 0
st,en = intervals[0]
for s,e in intervals[1:]:
if s<= en:
en = max(en,e)
else:
count +=1
st,en = s,e
return count+1 #最后一个区间没有被统计
a = int(input())
n = [tuple(map(int,input().split())) for _ in range(a)]
print(merge_intervals(n))

数据结构

链表(静态数组的实现)

  • idx的作用

    作为新结点的存储索引,保证将数据存到正确的位置

    每次新增节点都要将idx知道空位,自增

单链表

e = [0]*1010
ne = [0]*1010
head = -1
idx = 0
cnt = 0

#获取index位置的值
def get(index):
global head,e,ne,cnt
if index < 0 or index > cnt:
return -1
i = head
for _ in range(index):
i = ne[i]
return e[i]
#在头节点插入
def addAtHead(val):
global idx,e,ne,head,cnt
e[idx] = val
ne[idx] = ne[head]
head = idx
idx+=1
cnt+=1
#在index的前一个位置插入
def addAtIndex(index,val):
global idx,e,ne,head,cnt

if index > cnt:
return
if index <=0:
addAtHead(val)
return

i = head
for _ in range(index-1):
i = ne[i]

e[idx] = val
ne[idx] = ne[i]
ne[i] = idx
idx +=1
cnt+=1

#在尾部加入节点
def addAtTail(val):
addAtIndex(cnt,val)
return

#将index的值删除
def deleteAtIndex(index,val):
global ...
if index ==0:
head = ne[head] #直接更新head
return
if index >= cnt or index < 0:
return
i = head
for _ in range(index-1):
i = ne[i]

ne[i] = ne[ne[i]]

双链表

在向头节点插入元素的时候,需要注意的是原来链表中是否有值

以及删除的时候,需要注意删除的元素是否为最后一个元素

e = [0]*1010
ne = [-1]*1010
prev = [-1]*1010
head = -1
tail = -1
idx = 0
cnt =0







单调栈

处理下一个更大或者更小元素

最近更小/更大

直方图最大矩形面积

接雨水

有效括号

  • 递增,元素从底到顶,依次递增(删除比当前元素大的值)

def monotonic_stack(arr):
stack = []
res = [] #用来存储当前栈的变化
for x in arr:
while stack and stack[-1] > x: #如果栈顶元素大于当前元素,那么栈顶元素出栈,该元素入栈
stack.pop()
stack.append(x)
res.append(stack[:])
return res

  • 找左边第一个小于当前值的元素

    1. 栈储存的是下标

    2. 从左向右遍历,维护栈中对应元素是递增的

    3. 当遇到一个小于栈顶的元素,出栈,(栈为非空)那么此时的栈顶元素就是该元素左边第一个小的值

    def find_left_first_min(arr):
    n = len(arr)
    stack = []
    res = [-1]*n
    for i in range(n):
    while stack and arr[stack[-1]]> arr[i]:
    stack.pop()
    if stack:
    res[i] = arr[stack[-1]]
    stack.append(i)
    return res
  • 找右边第一个大于当前值的元素

    栈储存下标

    从左开始遍历,维护栈中其对应递减

    当遇到一个大于当前栈顶的元素,出栈,且这个元素就是当前栈顶最右边第一个大的值

    def find_right_first_max(arr):
    stack = []
    res = []
    for i in range(len(arr)):
    while stack and arr[stack[-1]] < arr[i]:
    res[stack.pop()] = arr[i]
    stack.append(i)
    return res

    有的题目可能问的是,距离

    右边res [stack.pop()] = (i-stack.pop())

    左边res[i] = i - stack[-1]

    还要注意res 的初始化

队列

单调队列

适用于滑动窗口

滑动窗口最大值

滑动窗口最小值

最短子数组和>=k


#单调队列
def monotonic_queue(arr):
from collections import deque
q = deque()
res = []

for x in arr:
while q and q[-1] > x: # 🔹 确保队列单调递增
q.pop()
q.append(x)
res.append(list(q)) # 记录每一步的队列状态

return res

# 测试
arr = [3, 1, 4, 2, 5]
print(monotonic_queue(arr))
  • 滑动窗口最大值

    1. 维护队列是递减
    2. 当长度i-d[0] >= k 将对首元素出队
    3. i >=k-1 就开始依次记录答案
    #滑动窗口最大值
    """给定一个数组nums ,和窗口大小k,找到每个窗口中的最大值
    用固定长度的队列来维护固定长度的数组
    要找最大值,将队列维护成递减,那么每个恒定长度的队列中,对首元素就是最大值

    """
    from collections import deque
    def max_sliding_window(nums,k):
    d = deque()
    res = []
    n = len(nums)

    for i in range(n):
    while d and nums[d[i]] < nums[i]:
    d.pop() #维护递减
    d.append(i)
    #如果当前对长度大于k,那么队首元素出队
    if i-d[0]>=k:
    d.popleft()
    #当i大于等于k-1时,记录答案
    if i >= k-1:
    res.append(nums[d[0]])
    return res
    nums = [1,3,-1,-3,5,3,6,7]
    k = 3
    print(max_sliding_window(nums,k))
    """
    [3,-1] [3]

    [3,-1,-3] [3,3]

    [5] [3,3,5]

    [5,3] [3,3,5,5]

    [6] [3,3,5,5,6]

    [7] [3,3,5,5,6,7]

    """


    最小和最大只是在维护队列递增和递减 的代码部分有差别

kmp

字符串匹配问题

  • next数组:本质上是一个前缀表

    作用:用来回退,当模式串与子串不匹配的时候,前缀表会告知我们从哪里开始重新匹配。

    本质:记录下标i 之前(包括i)的字符串中,有多大长度的相同前缀后缀

  • 最长公共前后缀:(字符串的前缀函数)

    1. 前缀:不包含最后一个字符,所有以第一个字符开头的连续字串
    2. 后缀:相反
  • 前缀表

    前缀表:其实就是模式串所符合前缀函数的一个数组

    使用:当我们对比文本串和模式串找到一个不匹配的字符,我们需要看前一个字符的在前缀表中所储存的(最长公共前后缀),而后将模式串从该下标对应的字符再开始匹配

前缀函数

使用kmp 思维 求'abcdabce'的前缀函数

pi 来存储字符串前缀函数的值,j 记录相匹配的个数

i 用来遍历字符串,对于每个i 有三个操作

  1. 不匹配的情况
  2. 匹配更新j
  3. 记录数组
def prefix_function(s):
n = len(s)
pi = [0]*n
j = 0
for i in range(n):
#不匹配
while j > 0 and s[j]!=s[i]:
j = pi[j-1]
#匹配,更新j
if s[j]==s[i]:
j+=1
#记录
pi[i] = j

return pi

kmp 算法

给定一个模式串S和文本串P,所有字符串只包含大小写英文字母以及阿拉伯数字

求出模式串再文本串中所有出现的位置的起始下标

  • 优化写法

    1. s,p 视为新字符串,求出新字符串的前缀函数lps
    2. 从后半部分(sz2+1)开始遍历lps 如果lps 中存在与sz2相等得值,说明在p 中存在完整地s
def find_occurrence(s,p):
cur = s+'#'+p
lps = prefix_function(cur)
res = []
sz1,zsz2 = len(p),len(p)

for i in range(sz2+1,sz1+sz2+1):
if lps[i] == sz2:
res.append(i-2*sz2)

return res



  1. i循环的区间: s 的结尾下标:sz2-1# 下标:sz2p 的起始下标:sz2+1
  2. 在 文本串中所匹配的下标:i - (sz2) -(sz2+1) + 1 = i-2*sz2
  • 经典写法(与前缀函数写法类似)

    这时的j 相当于指针——> 对应下标


    def kmp(s,p):
    pi = prefix_function(p) #模式串的前缀函数
    n1 ,n2 = len(s),len(p)
    res = []
    j = 0 #模式串的当前匹配位置
    for i in range(n1):
    #不匹配:j 回退
    while j >0 and s[i] !=p[j]:
    j = pi[j-1]
    #匹配 ;j+=1
    if s[i] == p[j]:
    j+=1
    #当找到一个完全匹配的记录
    if j == n2: # j == n2 而不是j == n2-1 因为当最后一位匹配(j = n2-1),j+=1 ——> j == n2
    res.append(i-n2+1) #记录起始位置
    j = pi[j-1] #继续匹配
    return res


TRIE树(字典树)

高效存储和查找字符串的数据结构

  1. son[p] 可以存储每个节点所包含的多个(>=0)字母对应的数字uu = ord(c)-ord('a')``son[p][u] 存储的是下一个节点的信息。或者认为 二维数组存储的是每个字母的不同映射

  2. cnt 将其看作标志也可以 ,如果是0 那么没有以这个为前缀的值,相反则有,数值为几就有几个

  3. idx 记录字典树的总结点的个数

n = 10010
son = [[0]*26 for _ in range(n)]
idx = 0
cnt = [0]*n

#插入操作
def insert(s):
global inx
p = 0 #从根节点开始
for c in s:
u = ord(c) - ord('a')
if son[p][u] ==0:
idx+=1
son[p][u] = idx
p = son[p][u]

cnt[p] +=1


#查找操作
def search(s):
p = 0
for c in s:
u = ord(c) - ord('a')
if son[p][u] == 0:
return False
p = son[p][u]
return cnt[p] >0



p = son[p][u] 即更新p 指向下一个节点

如果写成p = idx 那么会破坏结构

  • 维护数字数组的异或(相同为0,不同为1)极值

    将数字视为二进制,构造0-1 的数





并查集

用于管理元素所属集合的数据结构,实现为一个森林,每一棵树都是一个集合

支持两种操作:

  1. 合并: 合并两个元素所属的集合——> 合并两棵树

  2. 查询:查询某个元素所属的集合(对应树的根节点),用于判断两个元素是否在一个集合中

基本原理:每个集合用一颗树来表示,树根的编号就是整个集合的编号,每个节点存储他的双亲节点,p[x] 来表示x 的双亲节点

  1. 如何判断树根?if p[x] == x除了根节点,p[x]不会等于x
  2. 如何求x 的集合编号 ? while p[x] != x : x = p[x]
  3. 如何合并两个集合?把其中一棵树当作另一棵树的孩子即可

tips: 当其中一个节点找到了根节点,直接让这个节点的双亲结点也直接指向根节点 所以上面第二个问题可以通过路径压缩变成:p[x] = find(p[x],pa)

def dsu(n,pairs):
pa = list(range(n))
for p in pairs:
if p[0] == 'Mab':
union(p[1],p[2],pa)
else:
if find(p[1],pa) == find(p[2],pa):
print('YES')
else:
print('NO')

def find(x,pa):
if pa[x]!=x:
pa[x] = find(pa[x],pa)
return pa[x]

def union(x,y,pa):
pa[find(x,pa)] = find(y,pa)

直接导入import heapq 来进堆操作(小顶堆)

  1. 插入一个数
  2. 求集合中的最小值
  3. 删除任何一个元素
  4. 修改任意一个元素
  • heapq的相应用法

    import heapq 

    li = [10,20,15,30,40]

    heapq.heapify(li) #创建堆操作

    heapq.push() #入堆
    heapq.pop() #删除并返回堆中最小的元素,维护堆的结构
    heapq.peek() #查看最小的元素

    heapq.heappush(li,5) #
    heapq.heappop(li)


  • 建堆操作

    def inin_heap(arr):

    n=len(arr)
    for i in range(n//2-1,-1,-1):
    down(i,arr,n)
  • down(i,arr,size)

    def down(i,arr,size):
    while True:
    mini = i
    left = i*2+1
    right = i*2+2
    if l < size and arr[i] >= arr[left]:
    mini = left
    if r < size and arr[i] >= arr[right]:
    mini = right
    if i = mini : #这一步不要忘记了
    break
    arr[i],arr[mini] = arr[mini],arr[i]
    i = mini



  • up(i,arr,size)

def up(i,arr,size):
while True:
parent = (i-1)*2

if parent >= 0 and arr[parent] > arr[i]:
arr[parent],arr[i] = arr[i],arr[parent]
i = parent
else:
break
  • 插入一个数

    def push(x):
    arr.append(x)
    up(len(arr)-1,arr,len(arr))

  • 删除最小的数

    def pop():

    if len(arr) == 0:
    return
    minn = arr[0]

    arr[0] = arr[-1]
    arr.pop()
    if len(arr) >1:
    down(0,arr,len(arr))


堆排序

  1. 每次取出最大值——即堆顶arr[0]
  2. 把他放在最终排序的位置,也就是数组末尾 然后只处理剩下的[0-i] 大小的堆
  3. 维护堆

def heap_sort(arr):
n = len(arr)

for i in range(n//2-1,-1,-1):
down(i,arr,n)

#将堆顶元素与堆尾元素互换,维护最大堆
for i in range(n-1,-1,-1):
arr[0],arr[i] = arr[i],arr[0]
down(0,arr,i) #堆的范围是递减的,只处理剩下的部分

def down(i,arr,size):
#维护大顶堆
while True:
maxi = i
left = i*2+1
right = i*2+2

if left < size and arr[maxi] < arr[left]:
maxi = left
if right < size and arr[maxi] < arr[right]:
maxi = right
if maxi == i:
break
arr[maxi],arr[i] = arr[i],arr[maxi]
i = maxi
arr = [5,3,8,4,2,6,1]
heap_sort(arr)

for _ in range(n): arr[0],arr[-1]= arr[-1],arr[0] down(0,arr,n) 与之对比,改写法没有动态更改堆的大小,已排序的部分仍然会被影响


#升序
import heapq
def heap_sort(arr):
heapq.heapify(arr)
return [heapq.heappop(arr) for _ in range(5)]

#降序
import heapq
def heap_sort(arr):
arr = [-x for x in arr]
heapq.heapify(arr)
return [-heapq.heappop(arr) for _ in range()]

哈希表

普通实现

k = (x%mod+mod)%mod

  • 拉链法

h[k] 初始值都为-1 ,相当于单链表中的head用于储存每个桶里面的头节点

e[],ne[] 用来当作链表,需要注意的是,在插入操作,每次都相当于从头节点插入新节点

mod = 100010
ne =[0]*mod
e = [0]*mod
h = [-1]*mod
idx = 0

def insert(x):
k = (x%mod+mod)%mod
e[idx] = x
ne[idx] = h[k] #ne[idx] = head
h[k] = idx #head = idx
idx+=1
def find(x):
k = (x%mod+mod)%mod
i = h[k]
while i != -1:
if e[i] == x:
return True
else:
i = ne[i]
return False
  • 开放寻址法
mod = 200020
h = [-1]*mod

def insert(x):
k = (x%mod+mod)%mod
while h[k] != -1 and h[k]!= x:
k = (k+1)%mod
h[k] = x

def find(x):
k = (x%mod+mod)%mod
while h[k] != -1:
if h[k] != x:
k = (k+1)%mod
else:
return True
return False


字符串哈希

用于快速判断两个字符串是否相等。

  1. 前缀哈希h[i] 的递推公式: h[i] = (h[i-1]*B + ord(s[i-1])-ord('a')+1)%MOD

  2. 子串[l,r]的哈希:

hash(l,r) = (h[r+1] -h[l]*B^(r-l+1))%MOD

mod = 10**9+7
base = 131

def strinf_prefix_hash(s):
n = len(s)
h = [0]*(n+1) #前缀哈希 h[i] 表示s[0:i] 的哈希值
p = [1]*(n+1)

for i in range(1,n+1):
h[i] = (h[i-1]*base+ord(s[i-1])-ord('a')+1)%mod
p[i] = (p[i-1]*base) % mod
return h,p
def get_substring_hash(l,r,h,p):
return ((h[r+1]-h[l]*p[l-r+1]) % mod+mod )%mod

如果要对比两个字串是否相等,直接对比子串的哈希值即可

但是可能会出现 两个子串的哈希值相等,但是字串不等。

可以再取得一个哈希值 mod = 998244353

搜索与图论

DFS

(深度优先搜索)特点 : 调用自身,会对其访问过的点打上标记,确保每个点只访问一次。


DFS(v) // v 可以是图中的一个顶点,也可以是抽象的概念,如 dp 状态等。
在 v 上打访问标记
for u in v 的相邻节点
if u 没有打过访问标记 then
DFS(u)
end
end
end



  • 全排列问题(没有重复的数字)


    """给定一个数字N 要求输出1-n 的这些数字的全排列"""
    def dfs(n,path,used,res):
    if len(path) == n:
    res.append(path[:])
    return
    for i in range(1,n+1):
    if used[i]:
    continue
    used[i] = True
    path.append(i)
    dfs(n,path,used,res)
    path.pop()
    used[i] = False

    n = int(input())
    path = []
    used= [False]*(n+1)
    res = []
    dfs(n,path,used,res)
  • 全排列(有重复的数字,去重)


    def dfs(nums,path,used,res):
    if len(path) == len(nums):
    res.append(path[:])
    return
    for i in range(len(nums)):
    if used[i] :
    continue
    if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
    continue
    path.append(nums[i])
    used[i] = True
    dfs(nums,path,used,res)
    path.pop()
    used[i] = False


    nums = [1,1,2]
    path = []
    used = [False]*len(nums)
    res = []
    nums.sort() #排序才能让相同的元素相邻
    dfs(nums,path,used,res)





    used 一共就两种情况:

    1. True ——> continue

    2. False ——>该元素与上一个元素相同+上一个元素已经被使用过了

    ​ ——>该元素与上一个元素相同+上一个没被用——continue

    nums 数组需要排序——相同的元素才能相邻

在全排列中,我们在dfs 中传入的参:path,used 的值在递归过程中会动态修改,隐式传递状态变化

  • 子集和问题

  • n 皇后

    def solve_n_q(n):
    def dfs(row,cols,dg,udg,board,res):
    n = len(board)
    if row == n:
    res.append(["".join(row) for row in board])
    return

    #在当前行中,递归所有的列
    for col in range(n):
    if col in cols or (row-col) in dg or (row+col) in udg :
    continue
    board[row][col] = "Q"
    cols.add(col)
    dg.add(row-col)
    udg.add(row+col)
    dfs(row+1,cols,dg,udg,board,res)
    #回溯,撤销选择
    board[row][col] ="."
    cols.remove(col)
    dg.remove(row-col)
    udg.remove(row+col)

    board = [["."]*n for _ in range(n)]
    res = []
    dfs(0,set(),set(),set(),board,res)
    return res


    n = int(input())
    res = solve_n_q(n)
    for i in res:
    print(i)

    col,dg,udg初始为set()便于添加删除操作

    这里的递归是按照行

    注意函数的嵌套

BFS

思维是按层遍历

  • 走迷宫

    变量: (x,y,step) x,y 代表当前位置,step 走了多少步,用prev 来记录前一个点 prev[(x,y)] =(px,py) 表示(x,y) 来自于(px,py)

    """
    走迷宫问题
    给定一个n*m 的二维整数数组,用来表示一个迷宫,数组中只包含0,1, 0表示可以走的路
    1 表示墙壁,
    最初,人站在(1,1)的位置每次可以向上向下,左右任意方向移动一次,如果要移动到右下角的(n.m),
    至少移动几次?
    """

    from collections import deque

    def bfs_maze(maze):
    queue = deque([(0,0,0)]) #注意这里是传递一个列表,列表里面第一个值(0,0,0)
    n,m = len(maze),len(maze[0])
    directions = [(1,0),(-1,0),(0,1),(0,-1)]
    prev = {}


    while queue:
    x,y,step = queue.popleft()
    #到达终点的操作
    if (x,y) == (n-1,m-1):
    path = []
    while (x,y) in prev:
    path.append((x,y))
    x,y = prev[(x,y)]

    path.append((0,0))
    path.reverse()

    for i in path:
    print(i)
    print(step)
    return
    #没到终点,寻找下一个点
    for dx,dy in directions:
    nx,ny = x+dx,y+dy

    if 0<=nx<n and 0<=ny<m and maze[nx][ny] ==0 and (nx,ny) not in prev:
    queue.append((nx,ny,step+1))
    prev[(nx,ny)] = (x,y)
    print(-1)





易错点思考

为什么 BFS 适用于求最短路径,而 DFS 不能保证最短?

queue 为什么用 popleft() 而不是 pop()? 更改就变成了dfs

prev 在 BFS 代码中的作用是什么?它和 visited 有什么区别? 防止重复访问+路径回溯

为什么 prev 只记录第一次访问的前驱 (nx, ny) -> (x, y)

queue.append((nx, ny, step + 1)) 这里 step + 1 为什么能确保正确的步数? 因为是按照 拓展的

如何判断 BFS 什么时候终止? if - return 或者print(-1)

print(-1) 是在什么时候被触发的?

如果 maze 的起点 (0,0)1(墙壁),代码会发生什么?如何修正? 不需要修正 print(-1)

如果 maze 终点 (n-1,m-1)1(墙壁),会发生什么?如何处理? 不需要修正print(-1)

图存储遍历

存储方式与手动实现哈希表的拉链法类似

在这里叫做链式前向星存图

下标即节点值!!!

e 储存边索引i 对应的邻接节点的下标ne存储边索引i下一条邻接边的索引;这两个值都是和边有关

u表示当前节点的下标 j表示邻接节点的下标

from collections import deque
#邻接表
n = 100010
e = [0]*(2*n) #无向图
ne = [0]*(2*n)
h = [-1]*(n+1)
idx = 0
st = [False]*(n+1)

def add(a,b):
global idx
e.append(b)
ne.append(h[a])
h[a] = idx
idx+=1


#深度优先遍历,参数是节点下标,从1开始
def dfs(u):
st[u] = True
print(u)
#遍历u对应链上的元素
i = h[u]
while i != -1:
j = e[i]
if not st[j]:
dfs(j)
i = ne[i]



def bfs(start):
queue = deque([start])
st[start] = True

while queue:
u = queue.popleft()
print(u)

i = h[u]
while i != -1:
j = e[i]
if not st[j]: #没访问,加入队列
st[j] = True
queue.append(j)
i = ne[i]







dfs 时使用隐式(系统)栈,递归调用。

回溯在:递归完成之后,while 循环

u,start 都是指下标

  • 树的重心 (树是无向的,连通的图)

    1. 定义:如果在树中选择某个节点并删除,这棵树将分为若干棵子树,统计子树节点数并记录最大值。取遍树上所有节点,使此最大值取到最小的节点被称为整个树的重心。
    2. 整体思路是算出每个节点的子树节点值,然后选出最大值中最小的
    3. dfs 来计算向下的子树的最大值的大小,总点数-当前子树 = 向上子树大小
    4. best_partcentroid设置为全局变量,维护树中的dfs


    #树的重心
    """给定一颗树,树中包含n个结点,编号(1-n)
    和n-1 条无向边,找到树的中心点,并输出将中心删除,如果将这个点删除后,
    剩余各个连通块中点数的最大值偏小,那么这个结点被称之为树的重心
    第一行包含整数n代表树的节点数
    接下来的n-1 行,每行包含两个整数a,b 表示a,b 之间有一条边
    输出一个整数m,表示重心
    """

    n = int(input())
    e = []
    ne = []
    h = [-1]*(n+1)
    idx = 0
    st = [False]*(n+1)
    #设置全局变量
    best_part = n #初始化最优子树的大小
    centroid = -1 #存储重心节点

    #存储,添加边
    def add(a,b):
    global idx
    e.append(b)
    ne.append(h[a])
    h[a] = idx
    idx +=1

    e.append(a)
    ne.append(h[b])
    h[b] =idx
    idx+=1



    def dfs_root(u):
    st[u] = True
    sumn = 1
    max_p = 0

    i = h[u]
    while i !=-1:
    j = e[i] #找到一个相邻节点,开始往下搜
    if not st[j]:
    sub = dfs(j) #对这个相邻节点深度遍历
    max_p = max(max_p,sub)
    sumn +=s
    i = ne[i] #再去找
    max_p = max(max_p,n-sumn)

    if max_p < best_part:
    best_part = max_p
    centroid = u

    return sumn

    for _ in range(n-1):
    a,b = map(int,input().split())
    add(a,b)

    dfs_root(1)
    print(centroid)


  • 图中点的层次

    e,ne 的范围应该是 m

    要求最小距离,应当维护一个变量,用来计算到起点的距离dist = [-1]*(n+1)

    """给定一个n个点,m条边的有向图,
    图中可能存在重边和自环。
    所有边的长度都为1,点的编号是1-n
    求出1号点到n号点的最短距离,如果从1号点无法走到n号点,输出-1
    第一行包含这两个整数n,m
    接下来的m行,每行包含两个整数a,b,表示存在一条从a走到b长度为一的边
    输出一个整数,表示1号点到n号点的最短距离
    """
    from collections import deque
    n,m = map(int,input().split())
    e = []
    ne = []
    h = [-1]*(n+1)
    idx = 0
    dist = [-1]*(n+1)
    st = [Fasle]*(n+1)

    def add(a,b):
    global idx
    e.append(b)
    ne.append(h[a])
    h[a] = idx
    idx +=1
    def bfs_distance(start):
    queue = deque([start])
    st[start] = True
    dist[start] = 1

    while queue:
    u = queue.popleft()

    i = h[u]
    while i != -1:
    j = e[i]
    if not st[j]:
    queue.append(j)
    st[j] = True
    dist[j] = dist[u]+1
    i = ne[i]
    return

    for _ in range(m):
    a,b = map(int,input().split())
    add(a,b)
    bfs(1)
    print(dist[n])


拓扑排序

给一个有向无环图的所有结点排序,对其中的顶点排序,对于每一条边(u,v)来说,u 在 v 的前面

  1. 入度:指向该点的箭头,凡是入度为0 的点都可以作为起点。即入度为零的点才能入队

  2. 需要维护一个变量,来计算当前节点的入度数in_degree[]

  3. 处理重边——> 可以用一个集合edge_set = set()来记录添加的边,在add中可以根据是否在其中剔除重边的情况

#bfs 拓扑排序
"""给定一个n个点m条边的有向图,图中可能存在重边和自环
请输出任何一个该有向图的拓扑序列,如果拓扑序列不存在,输出-1
若一个由图中所有点构成的序列A满足,对于每个边,(x,y) 在A中x出现在y之前
则称A是该图的一个拓扑序列
第一行包含两个整数n,m
接下来的m行,每行包含两个整数x,y,表示存在一条从x到y的有向边
输出一个拓扑序列,如果不存在拓扑序列,输出-1
"""
from collections import deque
n,m = map(int,input().split())
e = [0]*m
ne = [-1]*m
h = [-1]*(n+1)
idx = 0
st = [False]*(n+1)
in_degree = [0]*(n+1)
edge_set = set()
#创建有向图
def add(a,b):
global idx
if (a,b) in edge_set:
return
e[idx] = b
ne[idx] = h[a]
h[a] = idx
idx+=1
in_degree[b]+=1
edge_set.add((a,b))
def topsort():
queue = deque()
res = []
#让所有度数为0的入队
for i in range(1,n+1):
if in_degree[i] == 0:
queue.append(i)

while queue:

u = queue.popleft()
res.append(u)
i = h[u]
while i != -1:
j = e[i]
in_degree[j] -=1
if in_degree[j]== 0:
queue.appen(j)
i = ne[i]

if len(res)== n:
print(" ".join(map(str,res)))
else:
print(-1)

for _ in range(m):
a,b = map(int,input().split())
add(a,b)
topsort()







最短路

松弛操作:对于边(u,v) dist[v] = min(dist[v],dist[u]+w(u,v))

朴素dijkstra 算法(邻接矩阵)

思路:

  1. 每次从未标记(visited[i] = False ) 中选择一个到原点距离最小的点i

dist[i])并且将其标记 u = i ; visited[u] = True

  1. 计算与u为临近结点的距离,然后更新这些点(v)到原点的距离:dist[v] = min(dist[v],dist[u]+graph[u][v])
  2. 在每次循环中u都要初始化为-1
#dijkstra 朴素算法求最短路
"""给定一个n个点m 条边的有向图,图中可能存在重边和自环
所有边权都为正数,求出从一号点到n好点的最短距离,如果从一号点无法走到n号点,输出-1
第一行包含两个整数n,m
接下来的m行,每行包含三个整数x,y,z,表示存在一条从x到y的有向边,权重为z

输出一个整数,表示1号到n号的最短距离dist[n]
如果路径不存在,输出-1
"""
n, m = map(int,input().split())
INF = float('inf')
graph = [[INF]*(n+1) for _ in range(n+1)]
#将对角线的位置初始化为0
for i in range(1,n+1):
graph[i][i] = 0

#读入输入,初始化邻接矩阵
for _ in range(m):
x,y,z = map(int,input().split())
graph[x][y] = min(graph[x][y],z) #为了处理重边的情况
#处理算法
def dijkstra(n):
#初始化,需要两个变量:返回过的点,该点到起始点的最短距离
visited = [False]*(n+1)
dist = [INF]*(n+1)
dist[1] = 0 #起点到起点的距离为0

for _ in range(n):
u = -1
for i in range(1,n+1):
if not visited[i] and (u==-1 or dist[u] > dist[i]):
u = i
if u ==-1 or dist[u] = INF :
break
visited[u] = True
for v in range(1,n+1):
if not visited[v] and graph[u][v] != INF:
dist[v] = min(dist[v],dist[u]+graph[u][v])
return dist[n] if dist[n] != INF else -1

print(dijkstra(n))










堆(优先队列)优化的dijkstra (链式前向星)

在堆中,我们储存(dist[i],i)

相比于朴素算法,在找出FALSE 中找最小值可以依靠最小堆,最后依据该点更新操作可以依靠入堆操作

#用堆优化(稀疏的图)
import heapq

INF = float('inf')
n,m = map(int,input().split())
h = [-1]*(n+1)
e = [0]*m
ne = [-1]*m
weight = [0]*m #记录权重
idx = 0

def add(a,b,w):
global idx
e[idx] = b
ne[idx] = h[a]
weight[idx] = w
h[a] = idx
idx+=1


#这里省略初始化邻接矩阵
def dijkstra(n):
dist = [INF]*(n+1)
visited = [False]*(n+1)
dist[1] = 0
heap = [(0,1)] #初始化堆

while heap:
distance,u = heapq.heappop(heap)
if visited[u]:
continue
visited[u] = True

i = h[u]
while i != -1:
j = e[i]
w = weight[i]
if not visited[j] and dist[j] > dist[u]+w:
dist[j] = dist[u]+w
heapq.heappush(heap,(dist[j],j))
i = ne[i]
return dist
print(dijkstra(n))

bellman-ford(存在负权)(列表)

dijkstra 不同的是,bellman-ford 是对所有边都进行松弛操作

  • 关于负环

如果存在一个负权环的时候,松弛操作死循环——> 不存在最短路

因此如果需要判断整个图上是否存在负环,最严谨的做法是建立一个超级源点,向图上每个节点连一条权值为 0 的边,然后以超级源点为起点执行 Bellman–Ford 算法。

或者说: 如果在循环n (点的个数),还能更新dist[v] 那么说明有负环正常操作的循环次数为n-1

  1. 在bellman-ford 中因为是每个边都要进行松弛操作,所以不需要进行图的初始化,仅仅为结构体即可 edge = [tuple(map(int,sys.stdin.readline().aplit()))for _ in range(m)]

  2. 不需要设置visited ,用一个标志flag = False 来检测某一次循环中,是否有更新,如果没有改变,可以提前终止循环

import sys
n,m = map(int,sys.stdin.readline().split())
edge = [tuple(map(int,sys.stdin.readline().split())) for _ in range(m)]

def bellman_ford (n,edge,start):
INF = float("inf")
dist = [INF]*(n+1)
dist[1] = 0

#进行n次循环,每次都对所有的边*松弛操作*
for _ in range(n-1):
flag = False
for a,b,w in edge:
if dist[a]!= INF and dist[b] > dist[a]+w: #这里必须要判断dist[a]!= INF ,防止 dist[a]+w 溢出
dist[b] = diat[a]+w
flag = True
if not flag:
break
for a,b,w in edge:
if dist[b] > dist[a]+w:
print("存在负环")
return dist[n]
print(bellman_ford(n,edge,1))

  • 有边数限制的最短路

    1. important!在有边数限制的最短路中,最重要的是要将dist 进行备份。

    让每一轮的dist[v] 只被上一个轮影响,否则可能在本轮传播的过程中受影响。

    1. 在有边限制的最短路中,我们不用去考虑有负环的情况
    2. 在有限制边的个数中,我们不能提前终止,所以不需要设置flag
    import sys
    n,m,k = map(int,sys.stdin.readline().split())
    edge = [tuple(map(int,sys.stdin.readline().split())) for _ in range(m)]

    def bellmen_ford(n,edge,start,k):
    INF = float("inf")
    dist = [INF]*(n+1)
    dist[start] = 0

    #进行k条边的松弛操作,注意要进行备份
    for _ in range(k):
    backup = dist[:] #注意备份
    for a,b,w in edge:
    if backup[a] != INF:
    dist[b] = min(dist[b],backup[a]+w)
    return dist[n] if dist[n] != INF else -1

spfa 算法(负权)(链式前向星)

是对bellman-ford 算法的队列优化,有时候我们不需要那么多的松弛操作

  1. 维护一个队列,里面的值都是必要进行松弛操作的点——> 通过队列来进行更新

  2. 当一个节点符合条件dist[j] > dist[u]+w

场景1:节点 j 不在队列中 → 更新距离并加入队列。

场景2:节点 j 已在队列中 → 仅更新距离,不入队(避免重复处理)

from collections import deque

n,m = map(int,input().split())
e = [0]*m
ne = [-1]*m
h = [-1]*(n+1)
idx = 0
weight = [0]*m


def add(a,b,w):
global idx
e[idx] = b
ne[idx] = h[a]
weight[idx] = w
h[a] = idx
idx+=1


def spfa(n,start):
INF = float("inf")
dist = [INF]*(n+1)
queue = deque()
queue.append(start)
dist[start] = 0
in_queue = [False]*(n+1)


while queue:
u = queue.popleft()
in_queue [u] = True

i = h[u]
while i != -1:
j = e[i]
w = weight[i]
if dist[j] > dist[u]+w:
dist[j] = dist[u]+w
if not in_queue[j]:
queue.append(j)
in_queue(j) = True
i = ne[i]
return dist[n]









spfa 也能适用于全为正权的题目中,代替dijkstra 算法

  • 如果要检测是否有负权环

    from collections import deque

    INF = float('inf')
    n,m = map(int,input().split())
    h = [-1]*(n+1)
    e = [0]*m
    ne = [-1]*m
    weight = [0]*m #记录权重
    idx = 0
    def add(a,b,w):
    global idx
    e[idx] = b
    ne[idx] = h[a]
    weight[idx] = w
    h[a] = idx
    idx+=1

    def spfa(n,start):
    cnt =[0]*(n+1)
    dist = [INF]*(n+1)
    in_queue = [False]*(n+1)
    in_queue[start] = True
    dist[start] = 0
    queue = deque() #初始化队列
    queue.append(start)



    while queue:
    u = queue.popleft()
    in_queue[u] = False

    i = h[u]
    while i != -1:
    j = e[i]
    w = weight[i]
    if dist[j] > dist[u]+w: #就算是在队列中,也要更新
    dist[j] = dist[u]+w
    if not in_queue[j]:
    queue.append(j)
    in_queue[j] = True
    cnt[j] = cnt[u]+1 #只有在新元素入队的时候更新cnt
    if cnt[j] > n :
    return "负权环存在"

    i = ne[i]
    return dist[n] if dist[n] != INF else "impossible"
    for _ in range(m):
    u,v,w = map(int,input().split())
    add(u,v,w)
    print(spfa(n,1))










数论

质数

分解质因数(base)

埃氏筛法


#埃氏筛法求质数
"""给定一个正整数n求出1——n 中的所有质数的个数"""
def find_prime(n):
is_prime = [True]*(n+1)
is_prime[0]=is_prime[1]=False

for i in range(2,int(n**0.5)+1):
if is_prime[i]:
for j in range(i*i,n+1,i):
is_prime[j] = False
return [i for i in range(2,n+1) if is_prime[i]]


线性筛法


#线性筛质数(空列表中添加质数)
def liner_prime(n):
is_prime = [True]*(n+1)
prime = []
for i in range(2,n+1):
if is_prime[i]:
prime.append(i)
for p in prime:
if i*p > n :
break
is_prime[i*p] = False
if i%p == 0:
"""
i%p == 0
意味着i 之前被p 筛选过了
由于p在列表中是由小到大排列的,此时i的最小质因子就是p,
i 乘以其他列表中的指数也一定会被p给筛掉
比如 4 %2 ==0 , [2,3]就不需要将p循环到3 再去筛选12 了直接break
"""
break

return prime

约数

试除法求约数


#试除法求约数
"""给定n个正整数ai,对于每个ai 求出他的所有约数"""
def find_divisions(n):
small_divisions = []
big_divisions = []
for i in range(1,int(n**0.5)+1):
if n%i == 0:
small_divisions.append(i)
if i!= n//i :
big_divisions.append(n//i)
return small_divisions + big_divisions[::-1]

def print_number(numbers):
for number in numbers:
divisions = find_divisions(number)
print(str(divisions))

if __name__ == "__main__":
numbers = list(map(int, input().split()))
print_number(numbers)


约数个数


#求约数的个数
"""给定n个正整数ai ,求出这些数的乘积的约数的个数"""
#正整数质因子分解,并将其储存在字典中

def prime_factors(n):
factor = {}
if n == 1: return factor
while n%2 == 0:
if 2 not in factor :
factor[2] = 0
factor[2] += 1
n//=2
#偶数处理完之后,仅仅剩余奇数。
p = 3
while p*p <= n:
while n%p == 0:
if p not in factor:
factor[p] = 0
factor[p] += 1
n//=p
p+=2
#当不符合循环条件(即为 p*p >n,且当前n 大于1),此时的n也是质数
if n > 1:
factor[n] = 1

return factor

#统计最终的指数(同样用哈希表)
def count_divisiors(numbers):
prime_count = {}

for num in numbers :
factors = prime_factors(num)
for key,value in factors.items():
if key not in prime_count:
prime_count[key] = 0
prime_count[key] += value

n = 1
for value in prime_count.values():
n *= (value + 1)
return n%(10**9+7)

if __name__ == "__main__":
numbers = []
while True:
try:
number = int(input())
numbers.append(number)
except ValueError:
break
print(count_divisiors(numbers))


约数之和

#求约数的和

"""给出n个正整数ai,求出这些数的乘积的约数的和"""

#正整数质因子分解,并将其储存在字典中

def prime_factors(n):
factor = {}
if n == 1: return factor
while n%2 == 0:
if 2 not in factor :
factor[2] = 0
factor[2] += 1
n//=2
#偶数处理完之后,仅仅剩余奇数。
p = 3
while p*p <= n:
while n%p == 0:
if p not in factor:
factor[p] = 0
factor[p] += 1
n//=p
p+=2
#当不符合循环条件(即为 p*p >n,且当前n 大于1),此时的n也是质数
if n > 1:
factor[n] = 1

return factor

def geometric_sequence(p,e):
return (p**(e+1)-1)/(p-1)

def count_divisiors(numbers):
# 统计每个数的因子个数
divisiors = {}
for num in numbers:
factors = prime_factors(num)
for key,value in factors.items():
if key not in divisiors:
divisiors[key] = 0
divisiors[key] += value

s = 1
for key,value in divisiors.items():
s*=geometric_sequence(key,value)
return s%(10**9+7)

if __name__ == "__main__":
numbers = []
while True:
try:
number = int(input())
numbers.append(number)
except ValueError:
break
print(count_divisiors(numbers))



欧拉函数

欧拉定理


#欧拉函数
"""求正整数n的欧拉函数的值"""
def euler_function(n):
ans = n
for i in range(2,int(n**0.5)+1):
if n%i == 0:
ans *= (1 - 1/i)
while n%i == 0:
n//=i
if n > 1:
ans *= (1 - 1/n)
return int(ans)
def every_euler_function(numbers):
for number in numbers:
print(euler_function(number))


if __name__ == "__main__":
numbers = []
while True:
try:
line = input().strip() # 去除首尾空白字符
if not line: # 如果是空行就退出
break
number = int(input())
numbers.append(number)
except ValueError:
break
every_euler_function(numbers)


筛法求欧拉函数


#筛法求多个数的欧拉函数
"""给一个正整数N ,求出1-N 中每个数的欧拉函数之和"""
def linear_euler_function(n):
#储存初始的欧拉函数值
phi = list(range(n+1))
is_prime = [True]*(n+1)
prime = []

for i in range(2,n+1):
if is_prime[i]:
prime.append(i)
phi[i] = i-1
for p in prime:
if i*p > n :
break
is_prime[i*p] = False
if i%p==0:
phi[i*p] = phi[i]*p

break #不需要在检查其他的p
else:
phi[i*p] = phi[i]*(p-1)
return sum(phi)



快速幂

快速幂(迭代递归)


#快速幂


#递归版本的快速幂 求解a^b
def binpow(a, b):
if b == 0:
return 1
res = binpow(a, b//2)
if b % 2 == 0:
return (res * res)
else:
return (res * res * a)
#迭代版本的快速幂
# b & 1 检查当前位是否为1
# b >>= 1 将b右移一位,相当于b = b // 2
def binpow(a,b):
res =1
while b >0 :
if b & 1:
res = res*a
a= a*a
b>>=1
return res


#模意义下取幂——> 将每个值都进行取模运算
"""计算x^n mod m"""

def binpow(a,b,m):
res = 1
a%=m #确保初始值在合理范围中
while b > 0:
if b&1:
res = res*a%m #防止中间结果溢出
a = a*a%m
b>>=1
return res
def binpow(a, b, p):
if b == 0:
return 1
res = binpow(a, b//2, p)
if b % 2 == 0:
return (res * res) % p
else:
return (res * res * a) % p


快速幂求逆元

#快速幂求逆元
"""给出N组ai pi, 其中pi 是质数(要用到费马定理) 求ai 模pi 的逆元"""
def binpow(a,b,p):
res = 1
a%=p #确保初始值在合理范围中
while b > 0:
if b&1:
res = res*a%p #防止中间结果溢出
a = a*a%p
b>>=1
return res
def binpow(a, b, p):
if b == 0:
return 1
res = binpow(a, b//2, p)
if b % 2 == 0:
return (res * res) % p
else:
return (res * res * a) % p


def inverse_mod(numbers):
for number in numbers :
a,p = number[0],number[1]
if number[0]%number[1]!=0: # p=2 的时候应该也为 impossible
print(binpow(a,p-2,p))

else:
print("impossible")

if __name__ == "__main__":
numbers = []
while True:
try:
"""
使用strip去除首尾空白字符
检查是否有空白行
检查是否有两个数字
将字符串转换为数字
line = input().strip()
if not line :
break
a , p = map(int,line.aplit())
numbers.append([a,p])
"""
line = input().strip()
if not line :
break #如果是空行就退出
number = line.split()
number.append([int(number[0]),int(number[1])])
except ValueError:
break
inverse_mod(numbers)

拓展欧几里得算法



#拓展欧几里得算法
"""给出n组正整数ai bi,求出每组ai bi对应的xi , yi 使得ai*xi + bi*yi = gcd(ai,bi)"""
def extend_gcd(a,b):
if b== 0:
return a,1,0 # 当在python 中用逗号分隔多个值的时候,python 会自动将之打包成一个元组
gcd,x1,y1 = extend_gcd(b,a%b)
x = y1
y = x1- (a//b)*y1
return gcd,x,y