📜  具有较大角值的最长子序列(1)

📅  最后修改于: 2023-12-03 15:22:35.673000             🧑  作者: Mango

具有较大角值的最长子序列

大家都知道最长公共子序列问题,但是你是否听说过具有较大角值的最长子序列问题?这是一道略微复杂的动态规划问题,需要一点点技巧和耐心才能解决。

问题描述

给定两个序列 $A$ 和 $B$,令 $\mathrm{ANG}(x)$ 表示一个字符 $x$ 在字母表中的角度值。例如,若字母表为 a~z,则 $\mathrm{ANG}(\mathrm{a}) = 0$,$\mathrm{ANG}(\mathrm{m}) = \frac{12}{25}\pi$。

定义 $A$ 的一个子序列 $S_A$ 和 $B$ 的一个子序列 $S_B$ 的角值和为 $\sum_{x\in S_A\cup S_B} \mathrm{ANG}(x)$。请你设计一个算法,求出 $A$ 和 $B$ 的一个最长公共子序列 $S$,使得 $S$ 的角值和尽可能大。

思路分析

最长公共子序列问题的经典动态规划算法已经被广泛应用,这里不再赘述。我们考虑如何将角值和的条件融入其中。

首先,我们定义状态 $f_{i,j,k}$ 表示 $A$ 的前 $i$ 个字符和 $B$ 的前 $j$ 个字符,且最后一个字符分别是 $A_i$ 和 $B_j$,当前最长公共子序列的角值和为 $k$。

不难发现,如果 $A_i=B_j$,我们只需要将 $f_{i-1,j-1,k-\mathrm{ANG}(A_i)}$ 加上 $\mathrm{ANG}(A_i)$ 即可得到 $f_{i,j,k}$。而如果 $A_i\neq B_j$,则 $f_{i,j,k}$ 可以由 $f_{i-1,j,k}$ 和 $f_{i,j-1,k}$ 中的较大值转移而来。

注意到 $k$ 不可能无限制的增大,因此我们可以对 $k$ 进行一定的限制,使得动态规划过程的空间复杂度和时间复杂度都可以接受。具体而言,我们可以选定一个上界 $K$,只保留状态 $f_{i,j,k}$ 中 $k\leq K$ 的那些状态,避免不必要的计算。

最终的答案就是 $f_{n_A,n_B,K}$ 中的最大值。

代码实现

下面为本题的 Python 代码实现:

def solve(A, B, K):
    NINF = -1e100
    nA, nB = len(A), len(B)
    f = [[NINF]*(K+1) for i in range(nB+1)]
    for i in range(nA+1):
        g = [[NINF]*(K+1) for j in range(nB+1)]
        for j in range(nB+1):
            for k in range(K+1):
                if j>0: g[j][k] = max(g[j][k], g[j-1][k])
                if k>=ANG[A[i-1]] and j>=1:
                    g[j][k] = max(g[j][k], f[j-1][k-ANG[A[i-1]]]+ANG[A[i-1]])
                if j>0 and k>=ANG[B[j-1]]:
                    g[j][k] = max(g[j][k], f[j][k-ANG[B[j-1]]]+ANG[B[j-1]])
        f = g
    return max(f[nB])

其中,ANG[x] 表示字符 x 的角值,预处理即可。注意 NINF 表示一个足够小的负无穷,在初始化状态时需要使用。