[区间dp] NC13230合并回文子串

    技术2022-07-11  74

    题面

    link    输入两个字符串A和B( ∣ A ∣ , ∣ B ∣ < 50 |A|, |B| < 50 A,B<50 ),合并成一个串C,属于A和B的字符在C中顺序保持不变。如"abc"和"xyz"可以被组合成"axbycz"或"abxcyz"等。    我们定义字符串的价值为其最长回文子串的长度(回文串表示从正反两边看完全一致的字符串,如"aba"和"xyyx")。需要求出所有可能的C中价值最大的字符串,输出这个最大价值。   

    分析

      若是这题暴力把所有合成的 C 串找出来,则共有 C 100 50 C_{100}^{50} C10050 种可能,这种指数复杂度是不能承受的。而类比找一个字符串的回文串,若是只是单个字符串,我们可以通过dp进行 O ( n 2 ) O(n^2) O(n2) 的算法:

    //字符串从s[1]开始存储,长度为len for(int L = 1; L <= len; L++) { for(int i = 1; i + L - 1 <= len; i++) { int j = i + L - 1; if(s[i] == s[j] && (L <= 2 || dp[i+1][j-1])) { dp[i][j] = 1; ans = max(ans, L); } } }

      若是要用动态规划,区间dp的思想是考虑从小规模的状态转移到大规模的状态,因此肯定是从长度较小的回文子串转移到长度较大的回文子串,即如果一个回文串首尾加上同样的字母,可以构成一个新的更长的回文串。   若是用 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示区间 [ i , j ] [i, j] [i,j] 构成的子串是不是回文串(01表示),那么一个区间是回文串有以下三种可能:   ① i = j i = j i=j,即单个字符是回文串;   ② i = j − 1 i = j - 1 i=j1 并且 s [ i ] = s [ j ] s[i] = s[j] s[i]=s[j],即两个一样的字符是回文串;   ③ i < j − 1 i < j - 1 i<j1 并且 s [ i ] = s [ j ] ,   d p [ i + 1 ] [ j − 1 ] = 1 s[i] = s[j],\ dp[i+1][j-1] = 1 s[i]=s[j] dp[i+1][j1]=1,即一个回文串首尾加上同样的字母,可以构成一个新的更长的回文串。   因此若是求单个字符串的回文子串,大循环是区间长度,小循环是区间起始位置进行双重循环就行,当然还有更优秀的马拉车算法( O ( n ) O(n) O(n)) 。      对于这一题,我们需要考虑的是两个字符串组合得到的回文串,则需要四维 d p [ i ] [ k ] [ j ] [ l ] dp[i][k][j][l] dp[i][k][j][l], 表示 A [ i ] [ k ] , B [ j ] [ l ] A[i][k], B[j][l] A[i][k],B[j][l] 是否可以组成一个回文串。记 l e n 1 = k − i + 1 ,   l e n 2 = l − j + 1 len_1 = k - i + 1, \ len_2 = l - j + 1 len1=ki+1 len2=lj+1, 同样地,我们需要考虑状态转移:   ①如果 l e n 1 + l e n 2 ≤ 1 len_1 + len_2 ≤ 1 len1+len21, 则 d p [ i ] [ k ] [ j ] [ l ] dp[i][k][j][l] dp[i][k][j][l] 为 1。等于 1 时两个字符串的截取一个为空,一个长度为1,自然是回文串;等于 0 时两个字符串都截取为空,其实是没有回文串的定义的,但是为了之后的转移方便(如从 d p [ 0 ] [ 0 ] [ 0 ] [ 0 ] dp[0][0][0][0] dp[0][0][0][0] 转移到 d p [ 1 ] [ 1 ] [ 1 ] [ 1 ] dp[1][1][1][1] dp[1][1][1][1]), 定义为1。   ②如果 l e n 1 > 1 len_1 > 1 len1>1, 那么对于 d p [ i ] [ k ] [ j ] [ l ] dp[i][k][j][l] dp[i][k][j][l], 若 A [ i ] = A [ k ] A[i] = A[k] A[i]=A[k] d p [ i + 1 ] [ k − 1 ] [ j ] [ l ] = 1 dp[i+1][k-1][j][l] = 1 dp[i+1][k1][j][l]=1,那么首尾加上 A [ i ] , A [ k ] A[i] ,A[k] A[i]A[k]还是回文串,则 d p [ i ] [ k ] [ j ] [ l ] = 1 dp[i][k][j][l] = 1 dp[i][k][j][l]=1 。同理对于 l e n 2 > 1 len_2 > 1 len2>1。   ③如果 l e n 1 > 0 len_1 > 0 len1>0 l e n 2 > 0 len_2 > 0 len2>0, 若 A [ i ] = B [ l ] A[i] = B[l] A[i]=B[l] d p [ i + 1 ] [ k ] [ j ] [ l − 1 ] = 1 dp[i+1][k][j][l-1] = 1 dp[i+1][k][j][l1]=1, 那么在首尾加上 A [ i ] , B [ l ] A[i], B[l] A[i],B[l] 还是回文串,则 d p [ i ] [ k ] [ j ] [ l ] = 1 dp[i][k][j][l] = 1 dp[i][k][j][l]=1 。同理对于 A [ k ] = B [ j ] A[k] = B[j] A[k]=B[j] d p [ i ] [ k − 1 ] [ j + 1 ] [ l ] = 1 dp[i][k-1][j+1][l] = 1 dp[i][k1][j+1][l]=1。      所以以上就得出了从小回文串转移到大回文串的区间动态规划过程。通过四重循环,前两重为A, B串截取的长度,后两重为 A, B串的区间起始位置,在计算时记录最大值就可以解决了。   

    代码

    #include <bits/stdc++.h> using namespace std; typedef unsigned long long ll; const int maxn = 1005; const int INF = 0x3f3f3f3f; const ll mod = 998244353; char a[52], b[52]; int f[52][52][52][52], len1, len2, ans; int main() { int t; scanf("%d", &t); while(t--) { ans = 1; scanf("%s %s", a + 1, b + 1); len1 = strlen(a + 1), len2 = strlen(b + 1); for(int k1 = 0; k1 <= len1; k1++) //前两重循环区间长度 for(int k2 = 0; k2 <= len2; k2++) for(int i = 1; i + k1 - 1 <= len1; i++) //后两重循环区间起始位置 for(int j = 1; j + k2 - 1 <= len2; j++) { int k = i + k1 - 1, l = j + k2 - 1; if(k1 + k2 <= 1) //根据状态转移方程得出 f[i][k][j][l] = 1; else { f[i][k][j][l] = 0; //由于有多次询问,因此需要清零防止上次的dp数据干扰 if(k1 > 1) f[i][k][j][l] |= (f[i+1][k-1][j][l] && a[i] == a[k]); if(k2 > 1) f[i][k][j][l] |= (f[i][k][j+1][l-1] && b[j] == b[l]); if(k1 && k2) f[i][k][j][l] |= (f[i+1][k][j][l-1] && a[i] == b[l]); if(k1 && k2) f[i][k][j][l] |= (f[i][k-1][j+1][l] && a[k] == b[j]); } if(f[i][k][j][l]) ans = max(ans, k1 + k2); } printf("%d\n", ans); } }

      除了转移方程,还需要特别注意动态规划过程边界的处理。

    Processed: 0.009, SQL: 9