二维dp简介
二维dp和普通dp本质一样,只是状态的维度变成二维,即,需要两个变量来定义子问题。
二维dp的更新可能会存在部分优化:前缀和、滚动数组等。
动态规划分析步骤(通用)
- 拆分子问题
将原问题拆解成子问题,找到问题之间的联系- 确定状态
此处的状态代指不同的问题,例如dp[x]表示上x台阶的方案数,其中x就是状态;确定状态就是确定问题需要几个维度的已知变量。一般是“前n个xxx为m的最大价值/最小价值/方案数”等- 状态转移方程
状态(子问题)之间如何转移,即一个状态由哪几个状态转移来,或者该状态可以转移到哪些状态- 实现
按照循环、记忆化搜索等方式求解最终状态。
例题分析
1536数字三角形
题目链接:1536数字三角形
状态1:
<i,j>表示从(i,j)往下走的最大和dpi,j = max(dpi+1,j,dpi+1,j+1) + ai.j
状态2:
<i,j>表示到达(i,j)的最大和dpi,j = max(dpi-1,j-1,dpi-1,j) + ai.j
#解法一
N=int(input())# 三角形行数
a=[]
dp=[[0]*(N) for i in range(N)]
for i in range(N):
a.append(list(map(int,input().split())))
# 状态转移方程
# dp[i][j]表示到达i,j的最大和
# dp[i][j]=max(dp[i-1][j],dp[i-1][j-1])+a[i][j]
# i从上往下更新,枚举每一行
for i in range(N):
#枚举第j列
for j in range(i+1):
#边界,指遍历到两个底角的情况
if j==0:
dp[i][j]=dp[i-1][j]+a[i][j]
elif j==i:
dp[i][j] = dp[i - 1][j-1] + a[i][j]
#底边中间的数
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j]
print(max(dp[N-1]))
#解法二
N=int(input())# 三角形行数
a=[]
dp=[[0]*(N) for i in range(N)]
for i in range(N):
a.append(list(map(int,input().split())))
#状态转移方程:dp[i][j]= max(dp[i+1][j],dp[i+1][j+1]) +a[i][j]
# print(a)
# print(dp)
#i从下往上更新,枚举每一行
for i in range(N-1,-1,-1):
for j in range(0,i+1):
if i==N-1:
dp[i][j]=a[i][j]
else:
dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j]
# print(dp[i][j])
print(dp[0][0])
389摆花
题目链接:389摆花
给定n种花,数量不超过ai,需要凑出m盆,求方案数
状态<i,j>:前i种花,数量为j盆的方案数:dp[n][m]
状态转移:dp[i][j] = dp[i-1][j] + dp[i-1][j-1] + … + dp[i-1][j-a[i]]
边界:dp[…][0] = 1
#n种花,m盆(位置)
n,m=map(int,input().split())
a=[0]+list(map(int,input().split()))
dp=[[0]*(m+1) for i in range(n+1)]
#边界
for i in range(n+1):
dp[i][0]=1
for i in range(1,n+1):
for j in range(1,m+1):
for k in range(min(a[i],j)+1):
dp[i][j]+=dp[i-1][j-k]
dp[i][j]%1000007
print(dp[n][m])
3711选数异或
题目链接:3711选数异或
状态:dp[i][j]表示前i个数字异或得到j的方案数
状态转移方程:dp[i][j] = dp[i-1]j + dp[i-1]j^a[i]
边界:dp[0][0] = 1
n, x = map(int, input().split())
a = [0] + list(map(int, input().split()))
# dp[i][j] 表示前i个数字异或得到j的方案数
dp = [[0] * 64 for i in range(n + 1)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(64):
dp[i][j] = (dp[i - 1][j] + dp[i - 1][j ^ a[i]]) % 998244353
print(dp[n][x])
# 以下是使用滚动数组的版本
n, x = map(int, input().split())
a = [0] + list(map(int, input().split()))
# dp[i%2][j] 表示前i个数字异或得到j的方案数(滚动数组)
dp = [[0] * 64 for i in range(2)]
dp[0][0] = 1
for i in range(1, n + 1):
for j in range(64):
dp[i % 2][j] = (dp[(i - 1) % 2][j] + dp[(i - 1) % 2][j ^ a[i]]) % 998244353
print(dp[n % 2][x])