牛客7502I - Subsequence Pair
题意
- 给出两个字符串 S 和 T(∣S∣≤2000,∣T∣≤2000)。
- 需要从 S 和 T 中分别选出一个子序列 x 和 y。
- 要求:x 的字典序 < y 的字典序,( 以下记为 lex(x)<lex(y) )。
- 求最大的 ∣x∣+∣y∣。
思路
发现性质
- 我们能发现,如果 lex(x)=lex(y),那么接下来可能有 lex(x)<lex(y)。
- 而如果 lex(x)<lex(y),那么接下来只可能 lex(x)<lex(y)。
错误思路
- 令 dp[i][j][0/1] 代表 S 匹配到第 i 位,T 匹配到第 j 位,选出来的子序列字典序 x=y 还是 x<y。dp 值代表最大的 ∣x∣+∣y∣。
- 这样,转移有:
- dp[i][j][0]←dp[p][q][0]
- dp[i][j][1]←dp[p][q][0]
- dp[i][j][1]←dp[p][q][1]
错误原因
- 注意看转移:dp[i][j][1]←dp[p][q][1]
- 考虑这种情况:
- S=
abcde
,T=abcde
- 某状态下 S5=T5 ,并且 dp[p][q][1] 代表的字符串 x 为
ab
,y 为 abcd
,满足 lex(x)<lex(y) 的性质。
- 如果追加
e
,那么 x 为 abe
,y 为 abcde
,显然不再满足 lex(x)<lex(y) 的性质。
- 错误的原因:这样的状态不足以表示字符串信息。
正确思路
- 先正向做一遍最长公共子序列,存入 dp 数组。
- 再反向做一遍DP,记 f[i][j] 为如果在 i 和 j 处出现 Si<Tj,但之前字典序相等,那么在这之后能匹配的最长的长度和。
- 当 Si=Tj 时,ans=max(ans,dp[i][j]+f[i+1][j+1])
代码
#include <cstdio>
#include <iostream>
#include <cstring>
const int N = 2010;
const int INF = 1e9;
using namespace std;
int f[N][N],f2[N][N];
int dp[N][N];
char S[N], T[N];
int n, m;
void Sol()
{
for (int i=0; i<=n+5; i++)
{
for (int j=0; j<=m+5; j++)
{
dp[i][j] = f[i][j] = 0;
f2[i][j] = -1;
}
}
for (int i=n; i>=1; i--)
{
for (int j=m; j>=1; j--)
{
if(S[i]==T[j])
f2[i][j] = f2[i+1][j+1];
else if(S[i]<T[j])
f2[i][j] = 1;
else if(S[i]>T[j])
f2[i][j] = 2;
if(f2[i][j]==0 || f2[i][j]==1)
{
f[i][j] = n-i+1 + m-j+1;
}
}
}
for (int i=n+1; i>=1; i--)
{
for (int j=m; j>=1; j--)
{
f[i][j] = max(f[i][j], max(f[i+1][j], f[i][j+1]));
f[i][j] = max(f[i][j], m-j+1);
}
}
int ans = max(f[1][1],m);
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
if(S[i] == T[j])
{
dp[i][j] = max(dp[i][j], dp[i-1][j-1]+1);
ans = max(ans, dp[i][j]*2 + f[i+1][j+1]);
}
else
{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
printf("%d\n",ans);
}
int main()
{
while (scanf("%s %s",S+1, T+1)!=EOF)
{
n = strlen(S+1);
m = strlen(T+1);
Sol();
}
return 0;
}