1 条题解
-
0
一、题意理解
我们有两个字符串 和 ,长度分别为 和 。
我们要构造一个字符串 ,长度为 ,并且 可以拆成两个不相交的子序列,一个等于 ,一个等于 。
换句话说, 是 和 的 交错(interleaving),保持 和 各自的字符顺序。
然后定义 的价值 = 最长回文子串的长度。
我们要在所有可能的 中,找到最大的价值,并输出它。
二、初步观察
- 因为 是 和 的交错,所以 的字符集是 和 的字符集的并。
- 最长回文子串可能完全来自 ,完全来自 ,或者跨越 和 的字符。
- 如果最长回文子串完全在 中,那么它的长度就是 的最长回文子串长度,与 无关;同理 。
- 如果回文子串同时包含 和 的字符,那么我们必须能在 中安排这些字符,使得它们形成一个回文,并且保持 和 各自的顺序。
三、关键思路
设 表示考虑 的子串 和 的子串 时,它们能形成的回文串的最大长度。
但这样状态太多: 太大( 时还行?,但 长度没说,假设 的话, 勉强可做)。
实际上更常见的做法是:
枚举回文串的中心(或中心区间),然后向两边扩展,同时从 和 中取字符,但要保持 和 的顺序。
我们可以用区间 DP:
设 表示用 和 这些字符(按顺序交错)能构成的最长回文子串长度。
转移:
- 如果 且 ,那么可以用它们作为回文的两端,然后 。
- 如果 且 ,那么 。
- 如果 且 ,那么 。
- 如果 且 ,那么 。
- 还有长度为 1 或 2 的回文情况。
但这样状态是 ,在 时是 ,转移 ,可以接受。
四、状态设计简化
更常见的做法是设 表示 和 能组成的最大回文长度。
初始化:
- 空串:0
- 单个字符(来自 或 ):1
转移:
- $dp[i][j][k][l] = \max(dp[i+1][j][k][l], dp[i][j-1][k][l], dp[i][j][k+1][l], dp[i][j][k][l-1])$(舍弃一端)
- 如果 且 ,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i+1][j-1][k][l])$
- 如果 且 ,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i][j][k+1][l-1])$
- 如果 ,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i+1][j][k][l-1])$
- 如果 ,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i][j-1][k+1][l])$
- 如果 且 且 ,这种情况其实包含在上面?不,这是两端分别来自 和 的开头,但必须 和 是各自第一个字符?其实我们枚举的是子串,所以这种情况就是 和 作两端,但 和 是各自子串的第一个字符, 和 是最后一个字符,所以这种情况就是 时,,但要求 且 。
实际上,为了编码方便,我们可以把 和 连接成一个字符串 长度 ,然后用区间 DP 表示 作为 的子串时,能否由 和 的子序列组成,并且是回文。但这样要记录 和 的选取情况,状态要加维度。
五、标准解法思路
已知这类题的常见解法:
设 表示 和 能形成的最大回文子串长度。
初始化:任何长度为 1 的子串(来自 或 )回文长度为 1。
转移:
- 从 取两端:若 且 ,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i+1][j-1][k][l] + 2)$
- 从 取两端:若 且 ,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i][j][k+1][l-1] + 2)$
- 从 取左, 取右:若 ,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i+1][j][k][l-1] + 2)$
- 从 取左, 取右:若 ,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i][j-1][k+1][l] + 2)$
同时,还可以舍弃一端: $f[i][j][k][l] = \max(f[i+1][j][k][l], f[i][j-1][k][l], f[i][j][k+1][l], f[i][j][k][l-1])$
答案就是 ,其中 。
六、样例验证
样例1:
a = "aa", b = "bb"最大回文:可以构成 "abba" 作为 的子串,长度 4。
样例2:
a = "a", b = "aaaabcaa"最大回文:可以构成 "aaa" 或 "abcba" 等,最长是 5。
七、算法复杂度
状态数 , 时 ,转移 ,可过。
八、代码实现(C++)
#include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 55; int f[N][N][N][N]; char a[N], b[N]; int main() { int T; cin >> T; while (T--) { cin >> (a + 1) >> (b + 1); int n = strlen(a + 1); int m = strlen(b + 1); memset(f, 0, sizeof f); for (int len1 = 0; len1 <= n; len1++) for (int len2 = 0; len2 <= m; len2++) for (int i = 1; i + len1 - 1 <= n; i++) for (int k = 1; k + len2 - 1 <= m; k++) { int j = i + len1 - 1; int l = k + len2 - 1; int &res = f[i][j][k][l]; if (len1 + len2 <= 1) { res = len1 + len2; continue; } // 舍弃一个字符 if (len1 > 0) { res = max(res, f[i+1][j][k][l]); res = max(res, f[i][j-1][k][l]); } if (len2 > 0) { res = max(res, f[i][j][k+1][l]); res = max(res, f[i][j][k][l-1]); } // 两端都选,且匹配 if (len1 > 1 && a[i] == a[j]) res = max(res, f[i+1][j-1][k][l] + 2); if (len2 > 1 && b[k] == b[l]) res = max(res, f[i][j][k+1][l-1] + 2); if (len1 > 0 && len2 > 0) { if (a[i] == b[l]) res = max(res, f[i+1][j][k][l-1] + 2); if (b[k] == a[j]) res = max(res, f[i][j-1][k+1][l] + 2); } } cout << f[1][n][1][m] << endl; } return 0; }
- 1
信息
- ID
- 3907
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者