蓝桥杯 Python组
c++ 数据量和耗时
On: 1s 处理 1e8的数据
Int 2^32: 42亿,-21亿--+21亿:2*1e9;
一维拷贝
g = [] l = list(g)二维拷贝
ll = [] for i in range(4): ll.append(list(src[i]))
1 递归与递推
1.1 递归:自己调用自己
关键:转换为树,自顶向下,深度优先搜索DFS
例题:斐波那契数列、枚举、排列、组合
001 指数型枚举

-
对于集合中的每个元素有选或不选两种方案
-
时间复杂度O(2n)
# 目标:输入n,返回{1,2...n}的子集
# 分析:
# -- 对集合中的n个数,每个有选或不选两种可能,共2^n中可能
def dfs(layer):
if layer > n:
for i in range(1, n+1):
if path[i] == 1:
print(i, end=' ') # 每次输出以空格分隔
print() # 换行
return
# 选
path[layer] = 1 # 改变状态
dfs(layer + 1) # 选下一个数
path[layer] = 0 # 回溯,恢复状态
# 不选
path[layer] = 2
dfs(layer + 1)
path[layer] = 0
n = int(input())
# st[i]表示数字i的状态: 0未读,1选,2不选
N = 20
path = [0] * N # N个0的数组
dfs(1)
002 排列型枚举

有顺序,增加used数组,判断深度上path是否用过
分支:遍历1~n,
# 目标:输入n,返回{1,2...n}的全排列,按字典序(小的在前)
# 分析:
# -- 判断数字是否用过,没有用过加入st[i], 并改变状态
def dfs(layer):
if (layer > n):
for x in path[1: n+1]:
print(x, end=' ')
print()
return
for i in range(1, n+1):
if not used[i]:
path[layer] = i
used[i] = True
dfs(layer+1)
# 回溯
path[layer] = 0
used[i] = False
n = int(input())
N = 10
path = [0] * N
used = [False] * (N)
dfs(1)
Permutations
from itertools import permutations
n = int(input())
s = [i for i in range(1,n+1)]
for var in permutations(s):
# var 是n个元素的元组
print(' '.join(map(str,var)))
003 组合型枚举
无顺序,增加起始点参数
分支:每次选m个,起点i从start~n遍历,bfs(u+1, i+1);
# 目标:输入n,返回m个数的全排列,按字典序(小的在前)
# 分析:
# -- n选m,从start开始往后选,就不会选重
def dfs(layer, start):
if layer > m:
for x in path[1: m + 1]:
print(x, end=' ')
print()
return
# 剪枝:不够凑m个就退出
# 已选layer-1个,后面还有n-start+1个
if layer + n - start < m:
return
for i in range(start, n + 1):
path[layer] = i
dfs(layer + 1, i + 1)
path[layer] = 0
n, m = map(int, input().split())
N = 30
path = [0] * N
dfs(1, 1)
Combinations
from itertools import combinations
n, m = map(int, input().split())
arr = [i for i in range(1, n + 1)]
for var in combinations(arr, m):
# var 是一个有m个元素的元组
print(' ' .join(map(str, var)))
004 带分数
- TLE:
# 目标:输出用1∼9不重不漏地组成带分数表示的全部种数。
# 分析:
# -- 1. dfs9个数字的全排列:9!种可能
# -- 2. 在每种可能里面组合abc: 隔板法:C(8,2)种可能 --> 计算每种可能:满足条件则输出
# 求[l,r]区间的值
def cal(l, r):
res = 0
for x in st[l: r+1]:
res = res * 10 + x
return res
# 9个数分隔成a, b, c; 8个空位选两个隔板C(8,2)
way = [0, 0, 0] # 记录隔板位置
def dfs_abc(layer, start):
if layer > 2:
l = way[1]
r = way[2]
if l > 6:
# n < 10^6, 不会到7位数
return
a = cal(1, l)
if a > n:
return
b = cal(l + 1, r)
c = cal(r + 1, 9)
if (n - a) * c == b:
global cnt
cnt += 1
return
# 剪枝:已选layer-1,可选8-start+1
# layer-1+8-start+1 < 2时,不够选
if layer + 6 < start:
return
for i in range(1, 9):
way[layer] = i
dfs_abc(layer + 1, start + 1)
way[layer] = 0
# 求9的全排列
def dfs_9(layer):
if layer > 9:
# 到达叶子节点,确定一个排列
dfs_abc(1, 1)
return
for i in range(1, 10):
if not used[i]:
st[layer] = i
used[i] = True
dfs_9(layer + 1)
st[layer] = 0
used[i] = False
n = 105
cnt = 0
N = 10
used = [False] * N
st = [0] * N
dfs_9(1)
print(cnt)
- 优化版
# 目标:输出用1∼9不重不漏地组成带分数表示的全部种数。
# 分析:
"""
没有用到n的信息
n = a + b / c
c * n = c * a + b;
dfs_a { dfs_c } --> dfs_c { check(a, c) }
1.搜索a的所有可能
2.在a的叶子节点搜索c的所有可能
3.计算出b看是否满足条件
"""
cnt = 0
n = int(input())
N = 10
# 记录1~9是否用过
used = [False] * N
def check(a, c):
b = (n - a) * c
# 如果有一个为0
if not a or not b or not c:
return False
# 在副本上操作, 不能直接相等,否则等价于引用
cpy_used = list(used)
# 检查b的每位数
while b:
x = b % 10 # 取个位
b //= 10 # 除以10
# x是0 或 x被用过
if not x or cpy_used[x]:
return False
cpy_used[x] = True
# 检查所有数字是否都被用
for i in range(1, 10):
if not cpy_used[i]:
return False
return True
def dfs_c(layer, a, c):
if layer > 9: return
if check(a, c):
global cnt
cnt += 1
for i in range(1, 10):
if not used[i]:
used[i] = True
dfs_c(layer+1, a, c*10+i)
used[i] = False
# layer: 已选个数
def dfs_a(layer, a):
if a >= n: return
# 除根节点外,所有节点搜索c
if a:
dfs_c(layer, a, 0)
for i in range(1, 10):
if not used[i]:
used[i] = True
dfs_a(layer+1, a*10+i)
used[i] = False
dfs_a(0, 0)
print(cnt)
1.2 递推:自底向上,迭代
005 斐波那契数列
# 目标:从 0 1 1 2 3 ...求 前 n 个斐波那契数。
N = 50
n = int(input())
a = [0] * N
a[0], a[1] = 0, 1
for i in range(2, n):
a[i] = a[i-2]+a[i-1]
for i in range(n):
print(a[i], end=' ')
# 快速幂模版 求m^k % p, O(logk)
def qmi(m, k, p):
res = 1
while k:
if k&1: # y&1 就是取y的二进制最后一位,用来判断是否为奇数
res = res * m % p
m = m * m % p
k = k >> 1 # 右移,就是 k//2
return res

# 快速幂求第n个斐波那契数0,1,1,2,3,5...; 对1000000007求余数 时间复杂度分析:O(logn)
pass
006 费解的开关
# 目标:输入n, 接下来n组5*5的10数据代表开关状态,
# 输出: 每一组最少几步变成全1, 结果不超过6,否则为-1
# 分析:顺序无关,偶数次等于0次,只控制第一行,其余由其正上方决定。
T = int(input())
# 利用偏移量操作开关及其上下左右共五个位置
dx, dy = [-1, 0, 0, 0, 1], [0, -1, 0, 1, 0]
def turn(g, x, y):
for i in range(5):
xi, yi = x + dx[i], y + dy[i]
# 判断是否越界
if xi < 0 or yi < 0 or xi >= 5 or yi >= 5: continue
# 切换开关
g[xi][yi] = 1 - g[xi][yi]
for t in range(T):
src = [] # 存输入初始状态
for j in range(5):
src.append([int(x) for x in list(input())])
res = 7 # 设一个初值 结果要在6步以内
# 第一行5个开关,每一个两种情况,共2^5种可能
# 从00000到11111对应每种开关状态
for op in range(32):
# 直接相等相当于引用,会改变原数,拷贝一份下来用
# g = list[src] // 在一维可以这样拷贝, 二维不行, 二维自行手动拷贝
g = []
for i in range(5): g.append(src[i][:])
step = 0 # 记录步数
# 枚举第一行
for i in range(5):
# 判断op第i位是否为1
if (op >> i) & 1:
step += 1
turn(g, 0, i)
# 剩余四行,根据上一行是否为0
for i in range(4):
for j in range(5):
if g[i][j] == 0:
step += 1
turn(g, i+1, j)
# 看最后一行还有0不
zero = False
for i in range(5):
if g[4][i] == 0:
zero = True
break
if not zero and res > step:
res = step
if res > 6:
res = -1
print(res)
# 读掉下一行开头的空行
if t < T - 1:
line = input()
007 翻硬币
g1 = list(input())
g2 = list(input())
def turn(g, i):
g[i] = chr(ord('o') + ord('*') - ord(g2[i]))
g[i+1] = chr(ord('o') + ord('*') - ord(g2[i+1]))
res = 0
for i in range(len(g1)):
if g1[i] != g2[i]:
turn(g2, i)
res += 1
print(res)
008 飞行员兄弟
# 目标:4*4,每次改变整行整列,求全亮时最小步数并按字典序输出
# 分析:枚举所有情况 2^16次方,满足条件的一定是按字典序
# --
def turn_one(g, x, y):
# 切换点(x, y)坐标
g[x][y] = chr(ord('+')+ord('-')-ord(g[x][y]))
def turn_all(g, x, y):
# 切换整行整列
for i in range(4):
turn_one(g, x, i)
turn_one(g, i, y)
# 点(x, y)切换了两次, 切换回来
turn_one(g, x, y)
# 输入原始序列
src = []
for i in range(4):
src.append(list(input()))
res = 17 # 存答案
# 16个位置,从全0到全1枚举: 1.切换开关
for t in range(1 << 16):
pair = [] # 存开关的坐标
step = 0 # 存开关的次数
# 在副本上操作
ll = []
for i in range(4): ll.append(src[i][:])
# 将16位二进制数t 应用到16个位置上:1开,0不动
for j in range(16):
if t >> j & 1:
x = j // 4 # 行号
y = j % 4 # 列好
"""
y:0: 1: 2: 3:
x:
0: 0 1 2 3
1: 4 5 6 7
2: 8 9 10 11
3: 12 13 14 15
"""
turn_all(ll, x, y)
pair.append((x, y))
step += 1
# 判断是否全亮
has_ans = True
for l1 in ll:
for x1 in l1:
if x1 == '+':
has_ans=False
break
# 判断当前步数step是不是至今最小的
if has_ans:
if res > step:
res = step
ans = pair
print(res)
for x, y in ans:
print(x+1, y+1)
- DFS
2 二分与前缀和
最值问题:
1. 二分:
1. 二段性、
2. 单调性:若E满足,当E' >= E,E'是否满足需要
2. dfs
3. dp
4. 贪心
2.1二分
- 确定一个区间[L, R],使得目标值target一定在区间中
- 找一个判断条件,满足:
- 使得该条件具有二段性
- 答案是二段性的分界点(分界点有左右两种情况)
- 分析中点M在该条件是否成立:更新区间
- 关键:如果是R = mid,则不做处理;如果是L = mid,则计算mid时需要+1。
009 数的范围
# 目标:给定升序排列的数组n以及q个查询,返回每个查询元素的起始和终止位置
# 分析:二分
# --
n, q = map(int, input().split())
# 输入n个数, 转化为整型
nums = [int(x) for x in list(input().split(' '))]
# 如果是R = mid,则不做处理;如果是L = mid,则计算mid时需要+1
def check(x):
# 1 大于等于x的左端点,
# 2 小于等于x的右端点
l, r = 0, n - 1
while l < r:
mid = (l + r) // 2
if nums[mid] >= x:
r = mid
else:
l = mid + 1
# 找到左端点后再找右端点
if nums[l] == x:
print(l, end=' ')
r = n - 1
while l < r:
mid = (l + r + 1) // 2
if nums[mid] > x:
r = mid - 1
else:
l = mid
print(r)
else:
print(-1, -1)
for t in range(q):
x = int(input())
check(x)
010 三次方根
# 目标:给定一个浮点数 n ,求它的三次方根
# 要求:−10000≤n≤10000, 保留6位小数
# --
n = float(input())
N = 1e4+5
l, r = -N, N
while r - l > 1e-10:
mid = (l + r) / 2
if mid ** 3 <= n:
l = mid
else:
r = mid
print("%.6f" % r)
011 机器人跳跃问题
# 目标:求满足条件的最小值
# 分析:现在为E,下一步为H,跳过去变化为2E-H
# -- 当E为输入最大值时,可以保证每次都大于0
# 因为E > H, 所以 2E-H 恒大于0
# 所以答案在0~max输入之间
n = int(input())
a = [int(x) for x in input().split()]
def check(E):
for i in a:
E = 2 * E - i
if (E < 0):
return False
elif E >= 1e5:
return True # 到达1e5就能保证后面不会小于0了
return True
l, r = 0, max(a)
while l < r:
mid = (l + r) // 2
if check(mid):
r = mid
else:
l = mid + 1
print(l)
012 四平方和
- 暴力
# 目标:输出和为n的四个数的平方和
# 分析:可能多个结果,字典序第一个
# --
from math import sqrt
n = int(input())
def main():
a = 0
while a*a <= n:
b = a
while a*a+b*b <= n:
c = b
while a*a+b*b+c*c <= n:
d = int(sqrt(n - a*a - b*b - c*c))
if a*a + b*b + c*c + d*d == n:
print(a, b, c, d)
return
c += 1
b += 1
a += 1
main()
- 哈希
n = int(input())
record = {} # 记录c*c+d*d
c = 0
while c*c <= n:
d = c
while c*c+d*d <=n:
# 第一次c*c+d*d的的字典序是最小的,后面再出现可以忽略
if c*c+d*d in record:
d += 1
continue
record[c*c+d*d]=c, d
c+=1
a = 0
while a*a <= n:
b = a
while a*a+b*b <= n:
if n - (a*a+b*b) in record:
c, d = record[n -(a*a+b*b)]
print(a,b,c,d)
exit()
b += 1
a += 1
- 二分TLE
# 目标:输出和为n的四个数的平方和
# 分析:可能多个结果,字典序第一个
# --
n = int(input())
def main():
record = []
c = 0
while c*c <= n:
d = c
while c*c + d*d <=n:
record.append((c*c+d*d,(c,d)))
d += 1
c += 1
record.sort()
a = 0
while a*a <= n:
b = a
while a*a+b*b <= n:
l, r = 0, len(record)-1
# 找record 满足条件的左端点,可能存在多个相等的和
while l < r:
mid = (l + r) //2
if record[mid][0] >= n - ( a*a+b*b):
r = mid
else:
l = mid + 1
c, d = record[r][1]
if a*a+b*b + record[r][0] == n:
print(a, b, c, d)
return
b += 1
a += 1
main()
013 分巧克力
# 目标:
# 分析:
# --
n, k = map(int, input().split())
a = []
for i in range(n):
x, y = map(int, input().split())
a.append((x, y))
def check(len):
cnt = 0
for x, y in a:
cnt += (x//len)*(y//len)
if cnt >= k:
return True
return False
l, r = 0, 1e5
while l < r:
mid = (l + r + 1) // 2
if(check(mid)):
l = mid
else:
r = mid-1
print("%d" % r)
2.2 前缀和
只适用于静态数组,不能修改
si=a1+a2+...+ai;
aL+a(L+1)+...+ aR = sR-s(L-1)
014 一维前缀和
# 目标:n个整数,m个lr询问,求区间和
# 分析:
# --
n, m = map(int, input().split())
a = [int(i) for i in input().split()]
s = [0]
sum = 0
for i in a:
sum += a
s.append(a)
# s[0, s1, s2, s3.....]
# s[l~r] = s[r]-s[l-1]
for i in range(m):
l, r = map(int, input().split())
print(s[r]-s[l-1])
015 二维前缀和
# 目标:
# 输入一个 n行 m列的整数矩阵,再输入 q个询问,
# 每个询问包含四个整数 x1,y1,x2,y2
# 表示一个子矩阵的左上角坐标和右下角坐标
# 分析:
# --
# 输入
n, m, q = map(int, input().split())
N = 1010
a = [[0]*N for i in range(N)]
s = [[0]*N for i in range(N)]
for i in range(1, n+1):
rows = list(map(int, input().split()))
for j in range(1, m+1):
a[i][j] = rows[j-1]
# 坐标i, j左上矩形的和
for i in range(1, n+1):
for j in range(1, m+1):
s[i][j] = s[i-1][j]+s[i][j-1]+a[i][j] - s[i-1][j-1]
# 查询
for i in range(q):
x1, y1, x2, y2 = map(int, input().split())
print(s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1])
016 激光炸弹
# s = [[0]*N]*N
# 二维数组大坑:浅拷贝,改一个都被改了,用下面方法定义
s = [[0]*N for _ in range(N)]
# 目标:
# 分析:
# --
# 0~5000 --> 1~5001
N = 5005
len = 5001
s = [[0]*N for _ in range(N)]
n, r = map(int, input().split())
# 计算矩形区间的和
def get(x1, y1, x2, y2):
return s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]
# 输入权值
for i in range(n):
x, y, w = map(int, input().split())
# 把位置从1,1开始
x += 1
y += 1
s[x][y] += w
r = min(r, len)
# 计算前缀和
for i in range(1, len+1):
for j in range(1, len+1):
s[i][j] += s[i-1][j] + s[i][j-1] - s[i-1][j-1]
if r >= len:
print(s[len][len])
exit()
res = 0
# 边长为r,ij为右下角的正方形和
for i in range(r, len+1):
for j in range(r, len+1):
num = get(i-r+1, j-r+1, i, j)
res = max(res, num)
print(res)
017 k倍区间
# 目标:枚举,超时
# 分析:
# --
n, k = map(int, input().split())
a = [0]*(n+1)
s = [0]*(n+1)
for i in range(1, n+1):
a[i] = int(input())
s[i] = s[i-1] + a[i]
cnt = 0
for i in range(1, n+1):
for j in range(1, i+1):
if(s[i]-s[j-1]) % k == 0:
cnt += 1
print(cnt)
# 目标:
# 分析:l 与 r 前缀和同余,则(l,r)是一个k倍区间
# --
n, k = map(int, input().split())
s = [0]*(n+1)
cnt = [0]*(n+1)
cnt[0]=1
res = 0
for i in range(1, n+1):
x = int(input())
s[i] = (s[i-1]+x) % k # l与r的前缀和同余,[l+1, r]是k倍区间
res += cnt[s[i]] #前面有多少个同余的,就有多少个k倍区间
cnt[s[i]] += 1
# 模为0时很特殊,从开头到模为0的这个区间也是k倍区间 上述只算了两个模为0之间的区间
# 例如 1, 3, 5 为0,cnt[0] = 3; 上述只算了(1,3)(3,5)(1,5)还应有(0,1)(0,3)(0,5)
# 要么开头cnt[0]设为1, 要么最后再加上一个cnt[0]
# res += cnt[0]
print(res)
3 数学与简单DP
3.1 数学
打表找规律
018 买不到的数目
# 在已知两个包装的数量时,
# 求最大不能组合出的数字
# 若pq互质,则pq不能凑出的最大数:
# (p-1)(q-1)-1;
n, m = map(int, input().split())
print((n-1)*(m-1)-1)
019 蚂蚁感冒
# 长100厘米的细长直杆子上有n只蚂蚁。
# 它们的头有的朝左,有的朝右。
# 每只蚂蚁都只能沿着杆子向前爬,速度是1厘米/秒
# 碰面时,它们会同时掉头往相反的方向爬行
# 输入第一个1只蚂蚁感冒了。碰面时,会传染。
# 求所有离开杆时感冒总数
# 关键:碰面掉头等价于穿过:
n = int(input())
a = [int(x) for x in input().split()]
# 负的向左,正的向右
# 绝对值是里左边的距离
# 以感冒虫子为中心, l:从左往右,r:从右往左
l, r =0, 0
for i in range(n):
if a[i] > 0 and abs(a[i]) < abs(a[0]):
l += 1
elif a[i] < 0 and abs(a[i]) > abs(a[0]):
r += 1
if (a[0] > 0 and r == 0) or (a[0] < 0 and l == 0):
cnt = 1
else:
cnt = l + r + 1
print(cnt)
020 饮料换购
n = int(input())
res = n
# n 为瓶盖数量
while n >= 3:
x = n // 3 # 换的新酒
res += x
n = x + n % 3
print(res)
3.2 简单DP
闫氏dp:从集合角度分析
- 化零为整
- 化整为零
问题种类:
- 最值:最大价值,最小花费:一个方案的某个属性
- 思路:枚举所有方案:指数级别==》超时
- 化零为整:挖掘不同方案的共同关系,同类方案放到一个集合
- 化整为零:对每个集合划分子集,分别求解
021 01背包
原型:组合模型:满足某种限制下的所有组合中最好的情况
优化降维:对代码作等价变形,与题目逻辑无关。

n, m = map(int, input().split())
v = [0]
w = [0]
for i in range(n):
vi, wi = map(int, input().split())
v.append(vi)
w.append(wi)
N = 1005
# 前i个物品体积不超过j的最大价值
f = [[0]*N for i in range(N)]
for i in range(1, n+1):
for j in range(1, m+1):
# 容量比a[i]大才能选i
if j >= v[i]:
f[i][j] = max(f[i-1][j], f[i-1][j-v[i]]+w[i])
else:
f[i][j] = f[i-1][j]
print(f[n][m])
022 摘花生

# 状态表示:
# 集合:f[i][j],所有到达i,j的方案的集合
# 属性:数量最大
# 状态计算:最后一步差异:
# 从上来:f[i-1][j]+a[i][j]
# 从左来:f[i][j-1]+a[i][j]
# 输入
N = 105
T = int(input())
for t in range(T):
R, C = map(int, input().split())
ll = [[0] * (C+1)]
for r in range(R):
l = list(map(int, input().split()))
l.insert(0,0)
ll.append(l)
for r in range(1, R + 1):
for c in range(1, C + 1):
ll[r][c] = max(ll[r-1][c], ll[r][c-1]) + ll[r][c]
print(ll[R][C])
023 最长上升子序列
# 表示 :
# 集合:f[i] 以i结尾序列的集合
# 属性:集合的cnt最大
#
# 计算:最后一步差异
n = int(input())
a = list(map(int, input().split()))
dp = [0] * n
maxv = 0
for i in range(n):
dp[i] = 1
for j in range(i):
if a[j] < a[i]:
dp[i] = max(dp[i], dp[j]+1)
maxv = max(maxv, dp[i])
print(maxv)
todo 024 地宫取宝

# DFS:
MOD = 1000000007
# 当前所在行、列、宝贝数量、最大价值
def process(curR, curC, curNum, curMax):
# 越界
if curR > n or curC > m or curNum > k:
return 0
# 到终点
if curR == n and curC == m:
# 选ij或者 不选ij
if curNum == k or (w[curR][curC] > curMax and curNum + 1 == k):
return 1
else:
return 0
# 可拿可不拿
if w[curR][curC] > curMax:
return process(curR, curC+1, curNum+1, w[curR][curC]) + process(curR+1, curC, curNum+1, w[curR][curC])+process(curR, curC+1, curNum, curMax) + process(curR+1, curC, curNum, curMax)
# 只能选择不拿
else:
return process(curR, curC+1, curNum, curMax) + process(curR+1, curC, curNum, curMax)
# n行m列,恰好为k件
n, m, k = map(int, input().split())
# 存每个格子的价值
w = [[0]*(m+1)]
# 输入价值
for i in range(n):
l = list(map(int, input().split()))
l.insert(0, 0)
w.append(l)
print(process(1, 1, 0, -1) % MOD)
# DP:
todo 025 波动数列

# 长为n, 和为s, 每一项比前一项增加a或减少b
# 输出符合条件的个数
n, s, a, b = map(int, input().split())
# 结果取模
MOD = 100000007
# 状态表示 f[i][j]
# - 集合:{只考虑前i项, 且当前总和除以n的余数是j}的方案的集合
# - 属性:cnt
#
# 状态计算
# - 状态划分:最后一个不同点
# - 最后一项是 +a的所有方案:f[i-1][(j-i*a)%n]
# - 最后一项是 -b的所有方案: f[i-1][(j+i*b)%n]
N = 1010
f = [[0]*N for _ in range(N)]
def get_mod(a, b):
return (a%b+b)%b
f[0][0] = 1
for i in range(1, n+1):
for j in range(n):
f[i][j] = (f[i - 1][get_mod(j - a * i, n)] + f[i - 1][get_mod(j + b * i, n)]) % MOD
print(f[n-1][get_mod(s, n)])
4 枚举模拟与排序
4.1 枚举
026 连号区间数
# 区间长度等于最大值最小值之差就是连号区间
n = int(input())
a = list(map(int, input().split()))
res = 0
N = 10010
for l in range(n):
maxv, minv = -N, N
for r in range(l, n):
maxv = max(maxv, a[r])
minv = min(minv, a[r])
if (maxv - minv == r - l):
res += 1
print(res)
027 递增三元组
# a中比b小的,c中比b大的
n = int(input())
def f(x):
return int(x)+1
a = list(map(f, input().split()))
b = list(map(f, input().split()))
c = list(map(f, input().split()))
# 数组cnt:i这个值出现的次数; s:前缀和 <=i 的次数
N = 100005
cnt = [0] * N
s = [0] * N
ass = [0] * N
css = [0] * N
# 求ass
for ai in a:
cnt[ai] += 1
for i in range(1, N):
s[i] = s[i-1] + cnt[i]
for i in range(n):
ass[i] = s[b[i]-1]
# 计数器归零
for i in range(N):
cnt[i] = 0
s[i] = 0
# 求css
for ci in c:
cnt[ci] += 1
for i in range(1, N):
s[i] = s[i-1]+cnt[i]
for i in range(n):
css[i] = s[N-1] - s[b[i]]
# ass逐位乘css
res = 0
for i in range(n):
res += ass[i] * css[i]
print(res)
028 特别数的和
n = int(input())
res = 0
def check(num):
while num:
x = num % 10
num //= 10
if x == 2 or x == 0 or x == 1 or x == 9:
return True
return False
for i in range(1, n + 1):
if check(i):
res += i
print(res)
029 错误票据
n = int(input())
a = []
for _ in range(n):
l = list(map(int, input().split()))
for ch in l:
a.append(ch)
a.sort()
# 断号、重号
m, n = 0, 0
for i in range(1, len(a)):
if a[i] - a[i-1] == 2:
m = a[i]-1
if a[i] == a[i-1]:
n = a[i]
print(m, n)
030 回文日期
# 年份 1000 --》 9999
# 翻转构造回文日期
# 判断日期是否合法
m1 = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def check(d):
y = d // 10000
day = d % 100
m = d % 10000 // 100
if m > 12 or m < 1:
return False
if m != 2 and day > m1[m]:
return False
if m==2:
if y % 400== 0 or (y % 4 == 0 and y % 100 != 0):
if day > 29:
return False
else:
if day > 28:
return False
return True
d1 = int(input())
d2 = int(input())
res = 0
for year in range(d1 // 10000, d2 // 10000 + 1):
date = year
while year:
date = date * 10 + year % 10
year //= 10
if date >= d1 and date <= d2 and check(date):
res += 1
# print(date)
print(res)
031归并排序
n = int(input())
a = [int(x) for x in input().split()]
a.insert(0, 0)
# 归并排序 二分+merge [left, right]
def merge(left, mid, right):
temp = []
i, j = left, mid+1
while i <= mid and j <= right:
if a[i] <= a[j]:
temp.append(a[i])
i += 1
else:
temp.append(a[j])
j+= 1
while i <= mid:
temp.append(a[i])
i += 1
while j <= right:
temp.append(a[j])
j += 1
for ch in temp:
a[left] = ch
left += 1
def MergeSort(l, r):
if l >= r:
return
mid = (l + r) // 2
MergeSort(l, mid)
MergeSort(mid+1, r)
merge(l, mid, r)
MergeSort(1, n)
for ch in a[1:]:
print(ch, end=' ')
4.2 模拟
032 移动距离
w, m, n = map(int, input().split())
def num2point(num):
r = num // w
c = num % w
# 偶数行 r为奇数
if r % 2:
c = w-c-1
return r, c
x1, y1 = num2point(m-1)
x2, y2 = num2point(n-1)
print(abs(x2-x1)+abs(y2-y1))
033 日期问题
var = list(map(int, input().split('/')))
days = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def check(y, m, d):
if m > 12 or m < 1 or d < 1:
return False
if m != 2 and d > days[m]:
return False
if m==2:
leap = y%400 == 0 or (y%4==0 and y%100)
if d > 28+leap:
return False
return True
for i in range(19600101, 20591231):
y1 = i // 10000
d = i % 100
m = i % 10000 // 100
if check(y1, m, d):
y = y1 % 100
if (var[0] ==y and var[1]==m and var[2]==d) or (var[0] ==m and var[1]==d and var[2]==y) or(var[0] ==d and var[1]==m and var[2]==y):
print("%d-%02d-%02d" % (y1,m,d))
034 航班时间
n = int(input())
# 一行时间换算成秒
# 10:19:19 20:41:24
# 22:19:04 16:41:09 (+1)
def str2s():
st = input()
res = 0
l = st.split(' ')
if len(l) > 2:
res = int(l[2][2])*24*3600
a = [int(x) for x in l[1].split(":")]
res += a[0]*3600+a[1]*60+a[2]
a = [int(x) for x in l[0].split(":")]
res -= a[0]*3600+a[1]*60+a[2]
return res
# 记录秒数换算
for _ in range(n):
s1 = str2s()
s2 = str2s()
ss = (s2+s1) // 2
h = ss // 3600
s = ss % 60
m = ss % 3600 // 60
print("%02d:%02d:%02d" % (h, m, s))
todo 035 外卖店优先级
todo 036 逆序对的数量
4.3 排序
5 树状数组与线段树
5.1 树状数组
根据问题所需操作确定数据结构
线段树是树状数组的子集,优先考虑树状数组,做不了在用线段树。
O(logn),支持操作:
- 单点修改:给某个位置上的树加上一个数
- 区间查询:求某个前缀和Si,则SL--R=SR-SL-1
配合差分思想可解决区间修改,单点查询
基本原理:
原数组:A[]
树状数组:C[]
- C[]下标必须从1开始;
- 序号按整除2的x次方分x层:二进制末尾k个0位于第k层,
- 奇数位与原数组相同;C[i]=A[i]
- 偶数位:x层:x+1个数的和

核心:
- C[x]= (x-2^k, x]的区间和 , 左开右闭; 2^k=lowbit(x); lowbit(x)=x&-x;
037 动态求连续区间和
def lowbit(x):
return x&(-x)
# 求1到x的前缀和
def getSum(x):
res = 0
while x > 0:
res += C[x]
x -= lowbit(x)
return res
# x加上v
def addV(x, v):
while x <= n:
C[x] += v
x += lowbit(x)
n, m = map(int, input().split())
a = [int(x) for x in input().split()]
a.insert(0, 0)
# 初始化,相当于初始c[i]都为0,每个位置加上a[i];
C = [0] * (n+1)
for i in range(1, n+1):
addV(i, a[i])
for _ in range(m):
k, a, b = map(int, input().split())
if k == 0:
print(getSum(b)-getSum(a-1))
else:
addV(a, b)
class Node():
def __init__(self):
self.l = 0
self.r = 0
self.val = 0
# 左右儿子更新父节点
def pushup(u):
tr[u].val = tr[u<<1].val+tr[u<<1|1].val
# 线段建立树
def build(u, l, r):
tr[u].l = l
tr[u].r = r
if l==r:
tr[u].val = arr[l]
return
mid = (l + r) >> 1
build(u << 1, l, mid)
build(u << 1| 1, mid+1, r)
pushup(u)
# 区间查询
def query(u, l, r):
# 包含区间之间返回
if l <= tr[u].l and r >= tr[u].r:
return tr[u].val
mid = tr[u].l + tr[u].r >> 1
res = 0
if l <= mid:
res += query(u<<1, l, r)
if r > mid:
res += query(u<<1|1, l, r)
return res
# 单点修改
def modify(u, x, v):
if tr[u].l == tr[u].r:
tr[u].val += v
return
mid = tr[u].l + tr[u].r >> 1
if x <= mid:
modify(u << 1, x, v)
else:
modify(u << 1|1, x, v)
pushup(u)
n, m = map(int, input().split())
tr = [Node() for _ in range(4*n)]
arr = [0]+[int(x) for x in input().split()]
build(1, 1, n)
for i in range(m):
a, b, c = map(int, input().split())
if a == 1:
modify(1, b, c)
else:
print(query(1, b, c))
038 数星星
# 关键:y坐标递增:相当于只用维护之前小于等于x坐标的
# 每次查询后添加进去--》树状数组
#
# 多少个点在1--x之间,就是多少级
def lowbit(x):
return x&(-x)
def tradd(x, v):
while x <= N:
tr[x] += v
x += lowbit(x)
def query(x):
res = 0
while x > 0:
res += tr[x]
x -= lowbit(x)
return res
N = 32010
tr = [0] * N
# 记录横坐标个数的前缀和
n = int(input())
# 存0~n-1的答案
ans = [0] * n
for _ in range(n):
x, y = map(int, input().split())
# 横坐标从0开始,树状数组从1开始
x += 1
# x插入数组前,【1到x】有query(x)个,x等级就是query(x);
ans[query(x)] += 1
# 插入后,个数加1
tradd(x, 1)
for i in range(n):
print(ans[i])
039 小朋友排队
def lowbit(x):
return x&(-x)
def tradd(x, v):
while x < N:
tr[x] += v
x += lowbit(x)
def query(x):
res = 0
while x > 0:
res += tr[x]
x -= lowbit(x)
return res
N = 1000010
n = int(input())
h = [0] + [int(x)+1 for x in input().split()]
Sum = [0] * N
tr = [0] * N
# 求前面有多少个大
for i in range(1, n+1):
Sum[i] = query(N-1) - query(h[i])
tradd(h[i], 1)
# 求后面有多少个小
tr = [0] * N
for i in range(n, 0, -1):
Sum[i] += query(h[i]-1)
tradd(h[i], 1)
res = 0
for i in range(1, n+1):
res += Sum[i]*(Sum[i]+1) //2
print(res)
5.2 线段树
可执行操作
- 求区间最大值
- 染色求面积
- 求最大连续和
原理:
完全二叉树(满二叉树引来,差的节点在最底层最右侧):维护一个序列
每个节点都是一个结构体,维护多个信息
根节点会存整个序列的某种属性,如总和、最值

操作
单点修改(递归)
- 递归到叶节点修改
- 回溯求节点左右儿子和
区间查询(递归)(某个区间总和,如【2到5】)
- 如果查询区间完全包含当前区间:直接返回
- 否则,递归到有交集的区间
code思路
// 1.用子节点信息更新当前节点信息
pushup()
// 2. 在一段区间上初始化线段树
build()
// 3 单点修改
modify()
// 4. 区间查询
query()
040 数列区间最大值
class Node():
def __init__(self):
self.l = 0
self.r = 0
self.maxv = 0
def pushup(u):
tr[u].maxv = max(tr[u<<1].maxv, tr[u<<1|1].maxv)
def build(u, l, r):
tr[u].l = l
tr[u].r = r
if l==r:
tr[u].maxv = arr[r]
return
mid = tr[u].l + tr[u].r >> 1
build(u<<1, l, mid)
build(u<<1|1, mid+1, r)
pushup(u)
# 此题不用单点修改
def modify(u, x, v):
pass
def query(u, l, r):
if l <= tr[u].l and r >= tr[u].r:
return tr[u].maxv
mid = tr[u].l + tr[u].r >> 1
res = 0
if l <= mid:
res = max(res, query(u<<1, l, r))
if r > mid:
res = max(res, query(u<<1|1, l, r))
return res
n, m = map(int, input().split())
N = 100010
arr = [0] + [int(x) for x in input().split()]
tr = [Node() for _ in range(4*n)]
build(1, 1, n)
for i in range(m):
x, y = map(int, input().split())
print(query(1, x, y))
todo 041 油漆面积
todo 042 三体攻击
043 螺旋折线
5.3 差分
给定原数组a[1], a[2]··· a[n]
构造差分数组b[1], b[2]··· b[n]
a数组是b数组的前缀和;
前缀和的逆运算:
b数组的初始化:
假定a数组全是0,b也为零,然后逐个添加。
核心操作:
将a[L~R]全部加上c,等价于b[L] +=c, b[R+1] -= c;
insert(int l, int r, int c)
{
b[l] += c;
b[r+1] -= c;
}
二维差分
insert(int x1, int y1, int x2, int y2, int c)
{
b[x1][y1] += c;
b[x1][y2+1] -= c;
b[x2+1][y1] -= c;
b[x2+1][y2+1] += c;
}
044 一维差分
045 二维差分
6 双指针、BFS与图论
6.1 双指针
只改变开头和结尾
// 核心代码, j在右, i在左
for (int i = 0, j = 0; j < n; j++)
{
//1. j点的状态考虑进来
//2. 关键 while 循环
while (i到j的区间不满足条件)
{
i++,i点的状态--;
}
//3. 如果满足条件,记录状态
if() st[j] = true;
}
6.2 BFS
思路:
bool st[N][N]; // 判重数组,入队判重
queue<-初始节点
while (queue不空)
{
auto t = q.front();
q.pop();
for(t相关的节点)
{
ver<-新节点
if(!st(ver))
{
ver<-入队尾
st[ver.x][ver.y]=true;
}
}
}
6.3 图论
7 贪心
局部最优-->全局最优
没有固定套路,结论证明困难
- 与做过题的类推,平时多记模型
- 猜,将n从1,2,3往上推
8 数论
最大公因数gcd,最小公倍数lcm
- 辗转相除法
gcd(a, b) = b?gcd(b, a%b):a;
- 线性筛求素数
// O(n)时间复杂度内
// 计算出1~n中所有素数,以及每个数的最小质因子
const int N = 1e6+10;
int primes[N], cnt; // 存所有质数
bool st[N]; // 是否筛过
int minp[N]; // 每个数的最小质因子
void get_primes(int n)
{
for (int i = 2; i <= n; i++)
{
// 没被筛就是素数
if (!st[i]) primes[cnt++] = i;
// 筛掉 之前所有素数与i的乘积, 筛掉的标记为true;
for (int j = 0; primes[j] * i <= n; j++)
{
st[primes[j] * i] = true;
minp[primes[j]*i] = primes[j];
// primes[j]一定不大于i的最小质因子;//筛掉的一定是和数,且一定是用最小质因子筛的
if (i % primes[j] == 0) break;
}
}
}
算数基本定理:
正整数可以唯一分解成p1^a1 *p2a2····piai;pi是质数;

浙公网安备 33010602011771号