Problem Set 7
Problem Set 7.1
Problem 7.1.1
设\(f_i\)表示将\(i\)变成\(1\)所需要的最小次数,有\(f_1=0\)
状态转移方程见代码
def min_operations(n):
if n == 1:
return 0
dp = [0] * (n + 1)
dp[1] = 0
for i in range(2, n + 1):
option1 = dp[i - 1] + 1
option2 = dp[i // 2] + 1 if i % 2 == 0 else float('inf')
option3 = dp[i // 3] + 1 if i % 3 == 0 else float('inf')
dp[i] = min(option1, option2, option3)
return dp[n]
Problem 7.1.2
设\(f_{i,j}\)表示考虑前\(i\)个数,是否可以选出一些数,使得这些数的和为\(j\)
状态转移方程为:\(f_{i,j}=f_{i-1,j}|f_{i-1,j-s_i}\),其中\(|\)表示按位或
注意这里是\(01\)背包,所以如果使用滚动数组的话,要使用倒序循环
边界条件见下面的代码
def subset_sum(A, S):
dp = [False] * (S + 1)
dp[0] = True
for num in A:
for j in range(S, num - 1, -1):
dp[j] = dp[j] or dp[j - num]
return dp[S]
Problem 7.2
Problem 7.2.1
(a).简单完全背包,代码见下
def can_change_unlimited(coins, v):
dp = [False] * (v + 1)
dp[0] = True
for coin in coins:
for j in range(coin, v + 1):
dp[j] = dp[j] or dp[j - coin]
return dp[v]
(b).简单\(01\)背包,代码见下
def can_change_limited(coins, v):
dp = [False] * (v + 1)
dp[0] = True
for coin in coins:
for j in range(v, coin - 1, -1):
dp[j] = dp[j] or dp[j - coin]
return dp[v]
(c).设\(f_{i,j,k}\)表示考虑前\(i\)个硬币,最多使用\(k\)个硬币,是否能够合成\(j\)
状态转移方程为:\(f_{i,j,k}=f_{i,j,k}|f_{i,j-x_i,k-1}\)
注意这里每个硬币可以使用无限次,所以必须使用正序循环
def can_change_k_coins(coins, v, k):
dp = [[False] * (k + 1) for _ in range(v + 1)]
dp[0][0] = True
for coin in coins:
for j in range(coin, v + 1): # 正序循环
for c in range(1, k + 1):
if j - coin >= 0 and c - 1 >= 0:
dp[j][c] = dp[j][c] or dp[j - coin][c - 1]
return any(dp[v][c] for c in range(1, k + 1))
Problem 7.2.2
(a).设\(f_{i,j}\)表示X前\(i\)个字符和\(Y\)前\(j\)个字符的LCS
状态转移方程为:
- 如果X的第\(i\)个字符等于Y的第\(j\)个字符,则\(f_{i,j}=\max(f_{i,j},f_{i-1,j-1}+1)\)
- 无论什么情况,都有\(f_{i,j}=\max(f_{i,j},\max(f_{i-1,j},f_{i,j-1}))\)
def lcs_standard(X, Y):
m, n = len(X), len(Y)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if X[i-1] == Y[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
(b).设\(f_{i,j}\)表示X前\(i\)个字符和\(Y\)前\(j\)个字符的LCS
状态转移方程为:
- 如果X的第\(i\)个字符等于Y的第\(j\)个字符,则\(f_{i,j}=\max(f_{i,j},f_{i,j-1}+1)\)
- 无论什么情况,都有\(f_{i,j}=\max(f_{i,j},\max(f_{i-1,j},f_{i,j-1}))\)
def lcs_x_repeat(X, Y):
m, n = len(X), len(Y)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(m+1):
for j in range(1, n+1):
if i > 0 and X[i-1] == Y[j-1]:
dp[i][j] = max(dp[i][j], dp[i][j-1] + 1)
if i > 0:
dp[i][j] = max(dp[i][j], dp[i-1][j])
dp[i][j] = max(dp[i][j], dp[i][j-1])
return dp[m][n]
(c).设\(f_{i,j,k}\)表示X前\(i\)个字符和\(Y\)前\(j\)个字符的LCS,要求X的第\(i\)个字符重复出现的次数不超过\(k\)(题目给的常数\(k\)在下文认为是\(K\))
状态转移方程为:
- 如果X的第\(i\)个字符等于Y的第\(j\)个字符,则
- \(f_{i,j,k}=\max(f_{i,j,k},f_{i,j-1,k-1}+1),k\gt 1\)
- \(f_{i,j,k}=\max(f_{i,j,k},f_{i-1,j-1,K}+1),k=1\)
- 无论什么情况,都有\(f_{i,j,k}=\max(f_{i,j,k},\max(f_{i-1,j,K},f_{i,j-1,k}))\)(注意这里的\(k\)和\(K\))
def lcs_with_repetition_limit(X, Y, K):
m, n = len(X), len(Y)
dp = [[[0 for _ in range(K+2)] for _ in range(n+1)] for _ in range(m+1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
for k in range(1, K + 1):
if X[i-1] == Y[j-1]:
if k > 1:
dp[i][j][k] = max(dp[i][j][k], dp[i][j-1][k-1] + 1)
else:
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][K] + 1)
dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][K], dp[i][j-1][k])
result = max(dp[m][n])
return result
Problem 7.2.3
(1).设\(f_{i,j}\)表示考虑前\(i\)个数,是否可以选出一些数,使得这些数的和为\(j\)
状态转移方程为:\(f_{i,j}=f_{i-1,j}|f_{i-1,j-s_i}\),其中\(|\)表示按位或
最后判断\(f_{n,\frac{\beta}{2}}\)是否为真即可
注意这里是\(01\)背包,所以如果使用滚动数组的话,要使用倒序循环
def can_partition(nums):
total = sum(nums)
if total % 2 != 0:
return False
target = total // 2
dp = [False] * (target + 1)
dp[0] = True
for num in nums:
for j in range(target, num - 1, -1):
dp[j] |= dp[j - num]
return dp[target]
(2).代码的输入依次如下:
- \(n\),输入规模为\(O(\log n)\)
- \(s_i\),输入规模为\(O(n\cdot\log\beta)\)
由上所述,输入的总规模为\(O(n\cdot\log\beta)\);而算法的复杂度为\(O(n\beta)=O(n2^{\log\beta})\),所以不是多项式算法,而是指数级别算法

浙公网安备 33010602011771号