1 条题解

  • 0
    @ 2025-10-23 19:54:20

    一、题意理解

    我们有两个字符串 aabb,长度分别为 a|a|b|b|

    我们要构造一个字符串 cc,长度为 a+b|a| + |b|,并且 cc 可以拆成两个不相交的子序列,一个等于 aa,一个等于 bb

    换句话说,ccaabb交错(interleaving),保持 aabb 各自的字符顺序。

    然后定义 cc 的价值 = 最长回文子串的长度。

    我们要在所有可能的 cc 中,找到最大的价值,并输出它。


    二、初步观察

    • 因为 ccaabb 的交错,所以 cc 的字符集是 aabb 的字符集的并。
    • 最长回文子串可能完全来自 aa,完全来自 bb,或者跨越 aabb 的字符。
    • 如果最长回文子串完全在 aa 中,那么它的长度就是 aa 的最长回文子串长度,与 bb 无关;同理 bb
    • 如果回文子串同时包含 aabb 的字符,那么我们必须能在 cc 中安排这些字符,使得它们形成一个回文,并且保持 aabb 各自的顺序。

    三、关键思路

    dp[i][j][x][y]dp[i][j][x][y] 表示考虑 aa 的子串 a[i..j]a[i..j]bb 的子串 b[x..y]b[x..y] 时,它们能形成的回文串的最大长度。

    但这样状态太多:O(n2m2)O(n^2 m^2) 太大(n,m50n, m \le 50 时还行?T=50T=50,但 a,ba,b 长度没说,假设 50\le 50 的话,504=6.25×10650^4 = 6.25\times 10^6 勉强可做)。

    实际上更常见的做法是:

    枚举回文串的中心(或中心区间),然后向两边扩展,同时从 aabb 中取字符,但要保持 aabb 的顺序。


    我们可以用区间 DP:

    f[l][r][p][q]f[l][r][p][q] 表示用 a[l..r]a[l..r]b[p..q]b[p..q] 这些字符(按顺序交错)能构成的最长回文子串长度。

    转移:

    • 如果 a[l]=a[r]a[l] = a[r]l<rl < r,那么可以用它们作为回文的两端,然后 f[l][r][p][q]=2+f[l+1][r1][p][q]f[l][r][p][q] = 2 + f[l+1][r-1][p][q]
    • 如果 a[l]=b[q]a[l] = b[q]qpq \ge p,那么 f[l][r][p][q]=2+f[l+1][r][p][q1]f[l][r][p][q] = 2 + f[l+1][r][p][q-1]
    • 如果 b[p]=a[r]b[p] = a[r]rlr \ge l,那么 f[l][r][p][q]=2+f[l][r1][p+1][q]f[l][r][p][q] = 2 + f[l][r-1][p+1][q]
    • 如果 b[p]=b[q]b[p] = b[q]p<qp < q,那么 f[l][r][p][q]=2+f[l][r][p+1][q1]f[l][r][p][q] = 2 + f[l][r][p+1][q-1]
    • 还有长度为 1 或 2 的回文情况。

    但这样状态是 O(n2m2)O(n^2 m^2),在 n,m50n,m \le 50 时是 504=6.25×10650^4 = 6.25\times 10^6,转移 O(1)O(1),可以接受。


    四、状态设计简化

    更常见的做法是设 dp[i][j][k][l]dp[i][j][k][l] 表示 a[i..j]a[i..j]b[k..l]b[k..l] 能组成的最大回文长度。

    初始化:

    • 空串:0
    • 单个字符(来自 aabb):1

    转移:

    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])$(舍弃一端)
    2. 如果 a[i]=a[j]a[i] = a[j]i<ji < j,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i+1][j-1][k][l])$
    3. 如果 b[k]=b[l]b[k] = b[l]k<lk < l,则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i][j][k+1][l-1])$
    4. 如果 a[i]=b[l]a[i] = b[l],则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i+1][j][k][l-1])$
    5. 如果 b[k]=a[j]b[k] = a[j],则 $dp[i][j][k][l] = \max(dp[i][j][k][l], 2 + dp[i][j-1][k+1][l])$
    6. 如果 a[i]=b[k]a[i] = b[k]iji \le jklk \le l,这种情况其实包含在上面?不,这是两端分别来自 aabb 的开头,但必须 iikk 是各自第一个字符?其实我们枚举的是子串,所以这种情况就是 a[i]a[i]b[k]b[k] 作两端,但 iikk 是各自子串的第一个字符,jjll 是最后一个字符,所以这种情况就是 a[i]=b[k]a[i]=b[k] 时,2+dp[i+1][j][k+1][l]2 + dp[i+1][j][k+1][l],但要求 iji \le jklk \le l

    实际上,为了编码方便,我们可以把 aabb 连接成一个字符串 ss 长度 n+mn+m,然后用区间 DP g[l][r]g[l][r] 表示 s[l..r]s[l..r] 作为 cc 的子串时,能否由 aabb 的子序列组成,并且是回文。但这样要记录 aabb 的选取情况,状态要加维度。


    五、标准解法思路

    已知这类题的常见解法:

    f[i][j][k][l]f[i][j][k][l] 表示 a[i..j]a[i..j]b[k..l]b[k..l] 能形成的最大回文子串长度。

    初始化:任何长度为 1 的子串(来自 aabb)回文长度为 1。

    转移:

    • aa 取两端:若 a[i]=a[j]a[i] = a[j]iji \le j,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i+1][j-1][k][l] + 2)$
    • bb 取两端:若 b[k]=b[l]b[k] = b[l]klk \le l,则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i][j][k+1][l-1] + 2)$
    • aa 取左,bb 取右:若 a[i]=b[l]a[i] = b[l],则 $f[i][j][k][l] = \max(f[i][j][k][l], f[i+1][j][k][l-1] + 2)$
    • bb 取左,aa 取右:若 b[k]=a[j]b[k] = a[j],则 $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])$

    答案就是 f[0][n1][0][m1]f[0][n-1][0][m-1],其中 n=a,m=bn = |a|, m = |b|


    六、样例验证

    样例1:

    a = "aa", b = "bb"
    

    最大回文:可以构成 "abba" 作为 cc 的子串,长度 4。

    样例2:

    a = "a", b = "aaaabcaa"
    

    最大回文:可以构成 "aaa" 或 "abcba" 等,最长是 5。


    七、算法复杂度

    状态数 O(n2m2)O(n^2 m^2)n,m50n,m \le 502500×2500=6.25×1062500 \times 2500 = 6.25\times 10^6,转移 O(1)O(1),可过。


    八、代码实现(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

    「美团 CodeM 初赛 Round A」合并回文子串

    信息

    ID
    3907
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者