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})\),所以不是多项式算法,而是指数级别算法

posted @ 2025-05-15 15:56  最爱丁珰  阅读(21)  评论(0)    收藏  举报