“动态规划”在蓝桥杯中的出题类型,主要为两种,
要格外注意,每一次 dp 的迭代更新,都是针对于当前位置下的“所有情况”进行的,
应着眼于当前位置的每一种情况。
类型一:一共有多少种情况?
1. 第十届JavaB组:平方拆分
难度:⭐⭐⭐
思考分析
square = []
for i in range(44+1):
square.append(i**2)
dp = [[0]*2020 for _ in range(len(square))]
dp[0][0] = 1 #初始条件:两数相等为一种情况
for i in range(1,len(square)):
for j in range(2019+1):
if j<square[i]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = dp[i-1][j] + dp[i-1][j-square[i]]
print(dp[-1][2019])
2. 第十三届C/C++B组:2022
难度:⭐⭐⭐⭐
思考分析(三维dp+三层循环)
可以将此题看成一个背包装物品,只能装10件物品,且重量恰好为2022.
这次的“背包问题”额外增加了对物品的数量限制,“二维dp”可能较难处理;
因此升级为“三维dp”,多一个维度来记录物品的数量。
表示在1~i个体积的物品中, 选取了j个物品, 和为k,数量的多少。
最后,dp[2022][10][2022] 表示:在2022件物品中,选择了10件物品,且和为2022,的数量。
"""索引作为推导过程 值作为种类"""
dp = [[[0]*2030 for _ in range(11)] for _ in range(2030)]
"""初始化:"在第1~i个体积的物品中, 选取了0个, 和为0"的情况为1"""
for i in range(2023):
dp[i][0][0] = 1
for i in range(1,2023):
for j in range(1,11):
for k in range(1,2023):
if k < i:
"""当前第i个物品的体积小于和k 只能照搬"""
dp[i][j][k] = dp[i-1][j][k]
else:
"""
在这i个物品中,
选择当前第i个物品, 则dp[i-1][j-1][k-i]
不选择当前第i个物品, 则直接照搬dp[i-1][j][k]
"""
dp[i][j][k] = dp[i-1][j][k] + dp[i-1][j-1][k-i]
print(dp[2022][10][2022])
3. 第十四届C/C++B组:子2023
难度:⭐⭐⭐⭐
思考分析
每多增加一个“2”,dp[0]+1,且对应的“202”的数目(dp[2])也会增加1*dp[1]个;
每多增加一个“0”,“20”的数目(dp[1])会增加1*dp[0]个;
每多增加一个“3”,“2023”的数目(dp[3])会增加多1*dp[2]个。
seq = ''
for i in range(1,2023+1):
seq += str(i)
s = ''
for t in seq:
if t in ['2','0','2','3']:
s += t
"""分别代表 2 20 202 2023"""
dp = [0]*4
for t in s:
if t=='2':
dp[0] += 1
dp[2] += dp[1]
if t=='0':
dp[1] += dp[0]
if t=='3':
dp[3] += dp[2]
print(dp[-1])
类型二:最值问题。
1. 第十四届pythonB组:翻转
难度:⭐⭐⭐⭐
思考分析
对于每个按顺序衔接的字符串而言,它一共有四种情况:翻转+翻转,正常+翻转,翻转+正常,正常+正常。
# 拼接字符长度最短
n = int(input())
s = []
for _ in range(n):
s.append(input())
dp1 = [2]*n #第一个字符串未翻转
dp2 = [2]*n #第一个字符串翻转
for i in range(1,n): #对于当前第i个字符串
dp1[i] = min(dp1[i-1]+2-(s[i-1][1]==s[i][0]),
dp2[i-1]+2-(s[i-1][0]==s[i][0]))
dp2[i] = min(dp1[i-1]+2-(s[i-1][1]==s[i][1]),
dp2[i-1]+2-(s[i-1][0]==s[i][1]))
print(min(dp1[-1],dp2[-1]))
2. 第十一届pythonB组:本质上升序列
难度:⭐⭐⭐
思考分析(具体可参考)
(蓝桥杯第十一届决赛)试题D:本质上升序列(动态规划)_蓝桥杯本质上升序列答案-CSDN博客
s = """tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl"""
dp = [1]*len(s)
for i in range(len(s)):
for j in range(i):
if s[i] > s[j]:
dp[i] += dp[j]
elif s[i] == s[j]:
dp[i] -= dp[j]
print(sum(dp))
3. 第八届C/C++B组:对局匹配
难度:⭐⭐⭐
思考分析
①输入里存在相同数字,相同数字可以同时出现在“匹配”中;因此需要对数字进行去重,并将其作为接下来每一步“动态规划”的weight。与此同时,这对输入进行了“平滑处理”。
②提取出不同的等差数列组,每组数的差值为k.(取到了所有数字)
分成k组:{0,0+k,0+2k,...}, {1,1+k,1+2k,...}, {2,2+k,2+2k,...}, {k-1, 2k-1, 3k-1,...}
③对每一组等差数列进行“动态规划”,遵循一个原则:不能选择相邻的数字。
n, k = map(int,input().split())
nums = list(map(int, input().split()))
if not k:
print(len(set(nums)))
else:
ans = 0
# 1.给数字们计数
cnts = [0]*(max(nums)+1)
for i in range(len(nums)):
cnts[nums[i]] += 1
# 2.提取出不同的等差数列组,每组的数差值为k
for i in range(k):
#记录每个数的出现次数
val = []
for j in range(i,len(cnts),k):
val.append(cnts[j])
#动态规划:不能选相邻位置
dp = [0]*len(val)
dp[0] = val[0]
dp[1] = max(val[0], val[1])
for j in range(2,len(val)):
dp[j] = max(val[j]+dp[j-2], dp[j-1])
#添加进总次数
ans += dp[-1]
print(ans)
4. 第十三届C/C++B组:费用报销
难度:⭐⭐⭐⭐
思考分析
①注意日期的限制条件,将所有日期都转换为天数;根据日期的大小对票据进行排序。
②结合题意,寻找”最大值“,因此只需要设置“一维dp”即可;若设置“二维dp”,会超时。
③寻找上一张时间最接近的票据,
import datetime
N, M, K = map(int, input().split())
dayV = []
tmp_days = datetime.datetime(2001,1,1) #随意取一个非闰年的年份
for i in range(N):
month, day, v = map(int, input().split())
# 将日期转化为天数
current_date = datetime.datetime(2001,month,day)
days = (current_date-tmp_days).days+1
dayV.append([days, v])
# 对日期进行排序
dayV.sort(key=lambda dayV:dayV[0])
dayV = [[0,0]] + dayV
dp = [0]*(N+1)
dp[1] = dayV[1][1]
for i in range(2,N+1):
# 寻找上一张时间最接近的票据
k = i-1
while k>0:
if dayV[i][0]-dayV[k][0] >= K:
break
k -= 1
# 找到票据 且 小于最大金额限制
if dp[k]+dayV[i][1]<=M:
dp[i] = max(dp[i-1], dp[k]+dayV[i][1])
print(max(dp))