蓝桥杯之算法模板题 Python版(上)

简介: 蓝桥杯之算法模板题 Python版(上)

记录一下算法模板题,这样方便查阅和学习,希望好好加油


线段树

import os
import sys
N,Q = map(int,input().split())
arr = [0]
arr.extend(list(map(int,input().split())))
def ls(p):return p<<1 # p//2
def rs(p):return p<<1|1 # p//2 + 1
tree = [0 for _ in range(N<<2)] # 一共有2n个节点
tag = [0 for _ in range(N<<2)] # lazy_tag标记
def pushdown(p,pl,pr):
  if tag[p]!=0:
    mid = pl + pr >> 1
    addtag(ls(p),pl,mid,tag[p])
    addtag(rs(p),mid+1,pr,tag[p])
    tag[p] = 0
    # tree[p] = tree[lr(p)] + tree[rs(p)]
def push_up(p):
  tree[p] = tree[ls(p)] + tree[rs(p)]
# 搭建线段树 节点的值是求和
def bulid(p,pl,pr):
  if pl == pr:
    # tree[p] = float('-inf')
    tree[p] = arr[pl]
    return 
  mid = pl + pr>>1
  bulid(ls(p),pl,mid)
  bulid(rs(p),mid+1,pr)
  # tree[p] = tree[pl] + tree[pr]
  push_up(p)
# 增加lazy_tag标记
def addtag(p,pl,pr,d):
  tag[p] += d
  tree[p] += d*(pr-pl+1)
def query(p,pl,pr,L,R): # L,R是查询区间
  # 当前节点在查询区间内
  if L <= pl and pr <= R: return tree[p]
  pushdown(p,pl,pr) # 标记向下传递
  res = 0
  mid = pl + pr >>1
  if L <= mid:
    res+=query(ls(p),pl,mid,L,R) 
  if R > mid:
    res+=query(rs(p),mid+1,pr,L,R)
  return res
# 更新线段树,也就是加上k L,R是更新区间
def update(p,pl,pr,L,R,d):
  if L<=pl and pr<=R:
    addtag(p,pl,pr,d)
    return
  pushdown(p,pl,pr) # 标记向下传递
  mid = pl + pr >> 1
  if L <= mid: update(ls(p),pl,mid,L,R,d)
  if R > mid: update(rs(p),mid+1,pr,L,R,d)
  push_up(p)
bulid(1,1,N)
for _ in range(Q):
  q = list(map(int,input().split()))
  if q[0] == 1:
    update(1,1,N,q[1],q[2],q[3])
  elif q[0] == 2:
    print(query(1,1,N,q[1],q[2]))
import os
import sys
def ls(p):return p<<1
def rs(p):return p<<1|1
def push_up(p):tree[p]=tree[rs(p)]+tree[ls(p)]
def build(p,pl,pr):
  if pl==pr:
    tree[p]=1
    return
  mid=(pl+pr)>>1
  build(ls(p),pl,mid)
  build(rs(p),mid+1,pr)
  push_up(p)
def addtag(p,pl,pr,d):
  tag[p]=d
  tree[p]=d*(pr-pl+1)
def push_down(p,pl,pr):
  if ~tag[p]!=0:
    mid=(pl+pr)>>1
    addtag(ls(p),pl,mid,tag[p])
    addtag(rs(p),mid+1,pr,tag[p])
    tag[p]=-1
# 把1变成0
def update0(p,pl,pr,cnt):
  if cnt==0:return
  if tree[p]==cnt:
    addtag(p,pl,pr,0)
    return
  mid=(pl+pr)>>1
  push_down(p,pl,pr)
  if tree[ls(p)]>cnt:update0(ls(p),pl,mid,cnt)
  else:
    cnt-=tree[ls(p)]
    addtag(ls(p),pl,mid,0)
    update0(rs(p),mid+1,pr,cnt)
  push_up(p)
# 把0变成1
def update1(p,pl,pr,cnt):
  if cnt==0:return
  if pr-pl+1-tree[p]==cnt:
    addtag(p,pl,pr,1)
    return
  mid=(pl+pr)>>1
  push_down(p,pl,pr)
  if mid-pl+1-tree[ls(p)]>cnt:update1(ls(p),pl,mid,cnt)
  else:
    cnt-=(mid-pl+1-tree[ls(p)])
    addtag(ls(p),pl,mid,1)
    update1(rs(p),mid+1,pr,cnt)
  push_up(p)
def query(p,pl,pr,L,R):
  if L<=pl and pr<=R:return tree[p]
  push_down(p,pl,pr)
  mid=(pl+pr)>>1
  res=0
  if L<=mid:res+=query(ls(p),pl,mid,L,R)
  if R>mid:res+=query(rs(p),mid+1,pr,L,R)
  return res
n,m=map(int,input().split())
tree=[0 for _ in range(n<<2)]
tag=[-1 for _ in range(n<<2)]
build(1,1,n)
for _ in range(m):
  op,num=map(int,input().split())
  if op==0:
    pos=n-tree[1]
    cnt=max(0,num-pos)
    update0(1,1,n,cnt)
  elif op==1:
    pos=tree[1]
    cnt=max(0,n-num+1-pos)
    update1(1,1,n,cnt)
ans1,ans2=[],[]
for i in range(1,n+1):
  if query(1,1,n,i,i)==0:ans1.append(i)
  else:ans2.append(i)
for x in ans1[::-1]+ans2:
  print(x,end=' ')

DP 动态规划


dp, LIS **


'''
https://www.lanqiao.cn/problems/1188/learning/
难度: 中等   标签: dp, LIS
'''
import os
import sys
import bisect
# 请在此输入您的代码
n = int(input())
a = list(map(int,input().split()))
dp = [float('inf')]*(n+1)
dp[0] = a[0]
for i in range(1,n):
    t = bisect.bisect_left(dp,a[i])
    dp[t] = a[i]
print(bisect.bisect_left(dp,float('inf')))


01背包


动态转移方程

image.png

# https://www.lanqiao.cn/problems/1174/learning/
# 难度: 简单   标签: dp, 背包, 01背包
import os
import sys
# 请在此输入您的代码
N,V = map(int,input().split())
# f[N][V]
# f[i][j] 代表 i 件物品 , 容量为 j 的时候,得到最大的价值
f = [[0]*(V+1) for _ in range(N+1)]
for i in range(1,N+1):
    # v为体积, w为价值
    v, w = map(int, input().split())
    for j in range(1,V+1):
        # 第i件物品有两种选择,
        # 第1种是选第i件物品,f[i-1][j]
        # 第2种是不选第i件物品,f[i-1][j]
        if j < v: 
            f[i][j] = f[i-1][j]
        else:
            f[i][j] = max(f[i-1][j], f[i-1][j-v] + w)
print(f[N][V])

完全背包


一般就是

image.png

不过可以转化为动态转移方程


image.png

'''
https://www.lanqiao.cn/problems/1175/learning/
难度: 简单   标签: DP, 背包, 完全背包
'''
import os
import sys
# 请在此输入您的代码
N,V = map(int,input().split())
# f[i][j]表示前i种,总体积不超过j,最大的总价值
f = [[0]*(V+1) for _ in range(N+1)]
# 完全背包问题,可以买多个
for i in range(1,N+1):
    v,w = map(int,input().split())
    for j in range(1,V+1): # 这里是体积的范围
        # t = j//v
        # f[i][j] = f[i-1][j]
        # for x in range(1,t+1): # x是第i件物品的数量
        #     f[i][j] = max(f[i][j],f[i-1][j-x*v]+w*x)
        # 优化后写法,已经把所有的也考虑进去了
        if j < v:
            f[i][j] = f[i-1][j] # 相当于不买第 i 件物品
        else:
            f[i][j] = max(f[i-1][j],f[i][j-v] + w) # 这里是比较买i件物品和不买i件物品
print(f[N][V])
# 压缩成一维
# dp = [0]*(V+1)
# v,w = [],[]
# for i in range(N):
#     v,w = map(int,input().split())
#     for j in range(1,V+1):
#         if j >= v:
#             dp[j] = max(dp[j-v] + w, dp[j])
# print(dp[V])


多重背包


对于多重背包来说,实际上与一般是一样的情形的构造,就是数量受限制了而已

不过多重背包也有一种二进制的压缩写法,复杂度就会更低一点

'''
https://www.lanqiao.cn/problems/1176/learning/
难度: 简单   标签: dp, 背包, 多重背包
'''
import os
import sys
# 请在此输入您的代码
N,V = map(int,input().split())
# 多重背包和完全背包是一样的构造方式
f = [[0]*(V+1) for _ in range(N+1)] 
for i in range(1,N+1):
    v,w,s = map(int,input().split())
    for j in range(1,V+1):
        # 多重背包的区别就是,不可以直接简化,因为数量已经有了限制,所以说独立一个循环进行判断和更新
        for k in range(s+1):
            if k*v <= j:
                # 这一部分判断,是否买k个
                f[i][j] = max(f[i][j],f[i-1][j-k*v] + k*w)
            else:
                break
print(f[N][V])

二进制写法


多重背包二进制还可以进行优化,要不然上一部分的复杂度有 On的三次方


会把多重背包转化为01背包,对于01背包就稍微简单一点


比如200个数量的物体,可以转为为1,2,4,8,…,64,74这么多组


因为1127的数可以由164构成,然后200-127 = 73


(1~127 都可以表示出来,选出任意一种与 73 组合就可以表示出 74 ~ 200 ,所以合起来就可以表示 1~200)。


相当于每个数量的为一组


# 多重背包二进制还可以进行优化,要不然上一部分的复杂度有 On的三次方
# 会把多重背包转化为01背包,对于01背包就稍微简单一点
# 比如200个数量的物体,可以转为为1,2,4,8,....,64,74这么多组
# 因为1~127的数可以由1~64构成,然后200-127 = 73 
# (1~127 都可以表示出来,选出任意一种与 73 组合就可以表示出 74 ~ 200 ,所以合起来就可以表示 1~200)。
# 相当于每个数量的为一组
N,V = map(int,input().split())
v = [0]*12010
w = [0]*12010
cnt = 0
for i in range(N):
    a,b,s = map(int,input().split())
    k = 1
    # 按照二进制的形式分组
    while k <= s:
        cnt += 1
        v[cnt] = k*a
        w[cnt] = k*b
        s = s - k
        k = k*2
    # 不能按照二进制分组的物体数量单独划分为一组。
    if k > 0:
        cnt += 1
        v[cnt] = a*s
        w[cnt] = b*s
f = [0]*(cnt+1)
# 分组后就等同于0 1 背包
for i in range(1,cnt + 1):
    for j in range(V,0,-1):
        if j >= v[i]:
            f[j] = max(f[j],f[j-v[i]] + w[i])
        else:
            break
print(f[V])


混合背包


其中又有混合背包问题,混合背包问题就是,我们的数量变成无限了,对于无限来说,我们就不可以直接利用二进制进行计算。


不过我思考了一下,除非说以我们的体积作为我们的上界,还是可以进行的

首先就是先根据一个无限的情况进行一个判断,这里是改完全背包的,因为这些都是变体

'''
https://www.lanqiao.cn/problems/1177/learning/
难度: 简单   标签: dp, 背包, 混合背包
'''
N,V = map(int,input().split())
# f[i][j]表示前i种,总体积不超过j,最大的总价值
f = [[0]*(V+1) for _ in range(N+1)]
for i in range(1,N+1):
    v,w,s = map(int,input().split())
    for j in range(1,V+1):
        # 多重背包的区别就是,不可以直接简化,因为数量已经有了限制,所以说独立一个循环进行判断和更新
        if s != 0:
            for k in range(s+1):
                if k*v <= j:
                    # 这一部分判断,是否买k个
                    f[i][j] = max(f[i][j],f[i-1][j-k*v] + k*w)
                else:
                    break
        else:
            k = 0
            while k*v <= j:
                f[i][j] = max(f[i][j],f[i-1][j-k*v] + k*w)
                k += 1
print(f[N][V])


如果s=0表示该商品有无限个,但实际背包不可能放入无限多个,在此假设最大个数为能够全部放入背包的数量

所以这个也可以通过调整一个上界进行,因为我们的体积是有限的,所以也可以调整多重背包的二进制进行修改


'''
https://www.lanqiao.cn/problems/1177/learning/
难度: 简单   标签: dp, 背包, 混合背包
'''
N,V = map(int,input().split())
v = [0]*12010
w = [0]*12010
cnt = 0
for i in range(N):
    a,b,s = map(int,input().split())
    if s == 0:
        s = V//a # 设置上界即可
    k = 1
    # 按照二进制的形式分组
    while k <= s:
        cnt += 1
        v[cnt] = k*a
        w[cnt] = k*b
        s = s - k
        k = k*2
    # 不能按照二进制分组的物体数量单独划分为一组。
    if k > 0:
        cnt += 1
        v[cnt] = a*s
        w[cnt] = b*s
f = [0]*(cnt+1)
# 分组后就等同于0 1 背包
for i in range(1,cnt + 1):
    for j in range(V,0,-1):
        if j >= v[i]:
            f[j] = max(f[j],f[j-v[i]] + w[i])
        else:
            break
print(f[V])


分组背包


其实就类似于01背包,对于一个物品有两种决策选或不选,但是分组背包是在01背包的基础上对物品进行了分组,并且每一组只能最多选择一个物品,所以我们不妨用01背包的思想去思考分组背包.


分析:我们设f[i][j]为当前考虑到了第i组物品,剩余容里为j的背包能装物品的最大价值,那么很容易想到我们需要去枚举第i组物品,考虑选哪一个物品时最优的(或者不选),状态转移方程就是


image.png

v[i][k]和w[i][k]分别表示第i组物品中第k个物品的体积和价值

for(int i=1;i<=n;i++)
   for(int j=0;j<=m;j++)
    for(int k=1;k<=s[i];k++)//s[i]表示第i组物品的个数
     if(j>=v[i][k])//剩余的背包容量j大于第i组的第k个物品的体积 
     {
        f[i][j] = max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
     }
'''
https://www.lanqiao.cn/problems/1178/learning/
难度: 简单   标签: dp, 背包, 分组背包
'''
N,V = map(int,input().split())
f = [0]*(V+1)
v = [[0]]
w = [[0]]
s = [0]
for i in range(1,N+1):
    s.append(int(input()))
    # 一组物品只能买一件
    vi = [0]
    wi = [0]
    for _ in range(s[i]):
        a,b = map(int,input().split())
        vi.append(a)
        wi.append(b)
    v.append(vi)
    w.append(wi)
# f[i][j] 代表在选择i组中, 体积不超过j的最大价值
f = [[0]*(V+1) for _ in range(N+1)]
for i in range(1,N+1): # 1~N 组数
    for j in range(V,-1,-1): # V~0 体积数
        for k in range(1,s[i]+1): # 1~i 分组的物品数
            if j >= v[i][k]:
                f[i][j] = max(f[i][j],f[i-1][j - v[i][k]] + w[i][k])
print(f[N][V])

还可以进行压缩成一维的,减少空间

for 所有的组k
  for v=V..0
    for 所有的i属于组k
      f[v]=max{f[v],f[v-c[i]]+w[i]}
# 压缩成一维
N,V = map(int,input().split())
f = [0]*(V+1)
v = [[0]]
w = [[0]]
s = [0]
for i in range(1,N+1):
    s.append(int(input()))
    # 一组物品只能买一件
    vi = [0]
    wi = [0]
    for _ in range(s[i]):
        a,b = map(int,input().split())
        vi.append(a)
        wi.append(b)
    v.append(vi)
    w.append(wi)
# f[i][j] 代表在选择i组中, 体积不超过j的最大价值
f = [0]*(V+1)
for i in range(1,N+1): # 1~N 组数
    for j in range(V,-1,-1): # V~0 体积数
        for k in range(1,s[i]+1): # 1~i 分组的物品数
            if j >= v[i][k]:
                f[j] = max(f[j],f[j - v[i][k]] + w[i][k])
print(f[V])


区间DP


一.什么是区间dp?


顾名思义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。主要是通过合并小区间的 最优解进而得出整个大区间上最优解的dp算法。


二.核心思路


既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,求解每个小区间的最优解,再合并小区间得到大区间即可。所以在代码实现上,我可以枚举区间长度len为每次分割成的小区间长度(由短到长不断合并),内层枚举该长度下可以的起点,自然终点也就明了了。然后在这个起点终点之间枚举分割点,求解这段小区间在某个分割点下的最优解。板子如下:

for(int len = 1;len<=n;len++){//枚举长度
        for(int j = 1;j+len<=n+1;j++){//枚举起点,ends<=n
            int ends = j+len - 1;
            for(int i = j;i<ends;i++){//枚举分割点,更新小区间最优解
                dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+something);
            }
        }
    }

三.朴素区间dp(n^3)


转移方程


dp[j][ends] = min(dp[j][ends],dp[j][i]+dp[i+1][ends]+weigth[i][ends]);


j~ends堆合并 = 较小的(原来, 分割点i坐部分重量 + 分割点i右边部分重量 + 合并后两堆总重量)

注:可以用sum[j] - sum[i - 1]表示i~j堆的重量!


博弈论

nim博弈


这实际上是一个尼姆博弈的问题,尼姆博弈就是一个两人博弈,2名玩家轮流从数堆物品中拿取一定数量的物品,每次拿取时先选择某一堆,再从中拿取任意数量个物品,至少拿1个,至多将这一堆物品全部拿走,不能不拿。拿到最后一个物品的玩家获胜。


尼姆博弈是一个两人博弈,2名玩家轮流从数堆物品中拿取一定数量的物品,每次拿取时先选择某一堆,再从中拿取任意数量个物品,至少拿1个,至多将这一堆物品全部拿走,不能不拿。拿到最后一个物品的玩家获胜。


两堆的情形


我们从最简单的两堆物品的情形开始分析,并使用二元组(n,m)来表示这两堆物品当前的数量。当(n,m)情形下先手方\后手方有必胜策略时,我们称(n,m)为制胜位置。


我们将说明:当n=m时,后手有必胜策略,否则先手有必胜策略。

当n=m=1时,先手方仅能够将其中一堆完全取走,故后手方只需要将另一堆的1个物品取走即可获胜。即(1,1)是后手方的制胜位置。

当n=m=k>1时,无论先手方选择哪一堆,取走多少数量的物品,后手方只需要选择另一堆,并取走相同数量的物品,就可以将物品堆的状态从(k,k)转换到(j,j),其中j<k。如此操作下去,由于两堆物品的数量保持相等且严格递减,故后手方玩家总能将物品堆的数量转换为(1,1)或(0,0)。由上面的讨论与游戏规则知,后手方必胜。即(n,m),n=m是后手方的制胜位置。

当时,先手方只需要从较多的一堆物品中拿取适量的物品,使得两物品堆物品数量一致,就将情形转换为了上述两种情况之一,且自身处于后手方。由上面的讨论知,先手方必胜。即(n,m),是先手方的制胜位置。


定义:物品堆的尼姆和是一个二进制数,它是由所有物品堆中物品的数量转换为二进制后,进行异或运算得到的。设有k个物品堆,分别有

a9dcd8ba7d784347907c3e1b57185ec3.png


相关文章
|
4天前
|
机器学习/深度学习 算法 数据可视化
Python 数据结构和算法实用指南(四)(4)
Python 数据结构和算法实用指南(四)
10 1
|
4天前
|
机器学习/深度学习 存储 算法
Python 数据结构和算法实用指南(四)(3)
Python 数据结构和算法实用指南(四)
14 1
|
4天前
|
存储 算法 搜索推荐
Python 数据结构和算法实用指南(四)(2)
Python 数据结构和算法实用指南(四)
10 0
|
4天前
|
存储 算法 Serverless
Python 数据结构和算法实用指南(四)(1)
Python 数据结构和算法实用指南(四)
14 0
|
4天前
|
存储 算法 搜索推荐
Python 数据结构和算法实用指南(三)(4)
Python 数据结构和算法实用指南(三)
10 1
|
4天前
|
存储 搜索推荐 算法
Python 数据结构和算法实用指南(三)(3)
Python 数据结构和算法实用指南(三)
10 1
|
4天前
|
存储 算法 前端开发
Python 数据结构和算法实用指南(三)(2)
Python 数据结构和算法实用指南(三)
10 1
|
4天前
|
存储 算法 编译器
Python 数据结构和算法实用指南(三)(1)
Python 数据结构和算法实用指南(三)
13 1
|
4天前
|
存储 算法 搜索推荐
Python 数据结构和算法实用指南(二)(4)
Python 数据结构和算法实用指南(二)
11 2
|
4天前
|
存储 XML 算法
Python 数据结构和算法实用指南(二)(3)
Python 数据结构和算法实用指南(二)
13 1