蓝桥杯 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 指数型枚举

QQ图片20220119114046.jpg

  • 对于集合中的每个元素有选或不选两种方案

  • 时间复杂度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 排列型枚举

深度优先遍历.png

有顺序,增加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

5eq68

# 快速幂求第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二分

  1. 确定一个区间[L, R],使得目标值target一定在区间中
  2. 找一个判断条件,满足:
    1. 使得该条件具有二段性
    2. 答案是二段性的分界点(分界点有左右两种情况)
  3. 分析中点M在该条件是否成立:更新区间
  4. 关键:如果是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:从集合角度分析

  1. 化零为整
  2. 化整为零

问题种类:

  1. 最值:最大价值,最小花费:一个方案的某个属性
    • 思路:枚举所有方案:指数级别==》超时
    • 化零为整:挖掘不同方案的共同关系,同类方案放到一个集合
    • 化整为零:对每个集合划分子集,分别求解

021 01背包

原型:组合模型:满足某种限制下的所有组合中最好的情况

优化降维:对代码作等价变形,与题目逻辑无关。

截屏2023-02-09 15.36.20.png

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 摘花生

截屏2023-02-10 10.45.34.png

# 状态表示:
#   集合: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 地宫取宝

截屏2023-02-10 19.31.21.png

# 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 波动数列

截屏2023-02-10 21.35.27.png

# 长为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),支持操作:

  1. 单点修改:给某个位置上的树加上一个数
  2. 区间查询:求某个前缀和Si,则SL--R=SR-SL-1

配合差分思想可解决区间修改,单点查询

基本原理:

原数组:A[]

树状数组:C[]

  • C[]下标必须从1开始;
  • 序号按整除2的x次方分x层:二进制末尾k个0位于第k层,
  • 奇数位与原数组相同;C[i]=A[i]
  • 偶数位:x层:x+1个数的和

![image-20230222103124170](/Users/aoyuan/Library/Application Support/typora-user-images/image-20230222103124170.png)

核心:

  • 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 线段树

可执行操作

  • 求区间最大值
  • 染色求面积
  • 求最大连续和

原理:

完全二叉树(满二叉树引来,差的节点在最底层最右侧):维护一个序列

每个节点都是一个结构体,维护多个信息

根节点会存整个序列的某种属性,如总和、最值

![image-20230223095750350](/Users/aoyuan/Library/Application Support/typora-user-images/image-20230223095750350.png)

操作

单点修改(递归)

  1. 递归到叶节点修改
  2. 回溯求节点左右儿子和

区间查询(递归)(某个区间总和,如【2到5】)

  1. 如果查询区间完全包含当前区间:直接返回
  2. 否则,递归到有交集的区间

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 贪心

局部最优-->全局最优

没有固定套路,结论证明困难

  1. 与做过题的类推,平时多记模型
  2. 猜,将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是质数;

posted @ 2023-04-13 22:32  Tr41n  阅读(1111)  评论(0)    收藏  举报