📜  DP 多级 (1)

📅  最后修改于: 2023-12-03 14:40:52.115000             🧑  作者: Mango

DP 多级

DP(动态规划)是一种常见的算法思想,用于解决各种计算问题,在进行一些递推或者优化问题的时候特别有用。在解决问题中,有些情况是需要进行多级 DP(动态规划)的,用于解决更为复杂的问题。

什么是 DP 多级

DP 多级是指在已有 DP 的基础上,再次进行一些子问题的递推,以获得更为全面的结果。多级 DP 的种类比较多,其中比较常见的种类为分组 DP、区间 DP、树形 DP、状态压缩 DP 等等。

分组 DP

分组 DP 又称划分型 DP,是指在已有的 DP 的基础上,进行新的子问题划分和求解。在分组 DP 中,通常将问题的数据集合划分成若干个不相交的子集,对每个子集进行 DP;在各个子问题求解完毕后,再针对子问题之间的关系进行合并或者递推。

状态设计

常见地,分组 DP 中的状态设计如下:

定义状态 $dp[i][j]$ 表示在前 $i$ 个数据中由前 $j$ 组数据所构成的答案,则状态转移方程为:

$dp[i][j] = \max{dp[k][j-1]+solve(k+1, i)}$

其中,$solve(k, i)$ 是一个求解 $[k, i]$ 内某一特殊问题的函数。

代码实现
dp = [[0]*(m+1) for _ in range(n+1)]
for i in range(1, n+1):
    for j in range(1, m+1):
        for k in range(j-1, i):
            dp[i][j] = max(dp[i][j], dp[k][j-1] + solve(k+1, i))
区间 DP

区间 DP 是指对区间上的所有可能情况进行递推求解的 DP 方式。在此类问题中,常常将问题转化成对区间的求解,然后再对区间不断求解。

状态设计

在区间 DP 中,常见地,状态的设计如下:

定义状态 $dp[i][j]$ 表示区间 $[i,j]$ 的答案,则状态转移方程为:

$dp[i][j] = \max_{k = i}^{j-1} {dp[i][k] + dp[k+1][j] + q[i] \cdot q[k+1] \cdot q[j+1]}$

其中,$q$ 数组是矩阵连乘问题中的矩阵大小,在此处是与对应的区间大小相互对应的。

代码实现
dp = [[0]*(n+2) for _ in range(n+2)]
for len in range(1, n):
    for i in range(1, n-len+1):
        j = i + len
        dp[i][j] = float("inf")
        for k in range(i, j):
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + q[i]*q[k+1]*q[j+1])
树形 DP

树形 DP 是指对树形结构的 DP,它利用树的特性逐步求解子问题,然后再向上合并。相较于区间 DP 和分组 DP 等,树形 DP 在状态转移方程设计上更为复杂,但同时解决的问题也更有广度和深度。

状态设计

常见地,树形 DP 中的状态设计如下:

定义状态 $f[u]$ 表示以 $u$ 为根的子树上的答案,则状态转移方程为:

$f[u] = \max{g[u],\ h[u]}$

其中,$g[u]$ 表示对当前结点 $u$ 的最大贡献,$h[u]$ 表示对整个以 $u$ 为根的子树的最大贡献。

代码实现
def dfs(u, fa):
    for v in u.edges:
        if v == fa: 
            continue
        dfs(v, u)
        g[u] = max(g[u], g[v] + u.v)
        h[u] = max(h[u], h[v])
    h[u] = max(h[u], g[u])
状态压缩 DP

状态压缩 DP 一般用于解决一些最短路问题、路径问题、旅行商问题等类型的问题。在此类问题中,每个状态通常可以通过一个二进制子集来表示,状态压缩 DP 主要就是利用状态压缩的特性,将问题的状态转化成一个由二进制位组成的状态码,然后再进行状态转移递推求解。

状态设计

在状态压缩 DP 中,通常将状态用 01 数组或者比特位来表示,例如:

定义状态 $dp[m][S]$ 表示在前 $m$ 个物品中,对于集合 $S$ 中已经装入的物品,所能获得的最大价值,则状态转移方程为:

$dp[m][S] = \max{dp[m-1][(S \backslash s) \cdot 2^k] + v[i]}$

其中,$v[i]$ 表示第 $i$ 个物品的价值,$s$ 表示 $i$ 在二进制状态下所在的位置,$k$ 表示 $2^j$,用于表示状态 $S$ 中 $s$ 所对应的二进制位是 1 还是 0。

代码实现
dp = [[-1]*2**n for _ in range(m)]
dp[0][0] = 0
for i in range(1,m):
    for j in range(2**n):
        for k in range(n):
            if j&(1<<k):
                dp[i][j] = max(dp[i][j],dp[i-1][j-(1<<k)]+v[i][k])