题目大意:给出两个字符串,求出满足下列条件的最长的公共子序列:
①公共子序列连续的部分长度>=3
②断开处单调递增
分析:看起来好像经典问题最长公共子序列——但是不是。一开始想到直接写转移方程,发现如果前面长度为2,1之类,即使和后面连起来长度大于3了,这种状态也不能被找到;又想到先求最长公共子序列,然后减去小于3 的段,但是发现不仅程序中定位断开部分难写,而且会产生错解。
所以接着第一个思路,dp[i][j]之前连的串一定是一个连续公共的,并且我们可以选择它的长度为3..maxlength
预处理f[i][j]表示以a[i] b[j]结尾的串的公共后缀长度(二维DP)
然后开始状态转移,要注意的是开0 1两维,0表示这一位不一定要选的最大值,1表示这一位一定要选的最大值(前提是能选,不能的话,dp[i][j][1]初始为0)
关于枚举要连接的长度,这可能让复杂度达到O(N^3)
看了题解感觉真心巧妙:dp[i][j]=max(dp[i-k][j-k]+k) (f[i][j]=>k>=3)
即dp[i][j]=max(dp[i-3][j-3]+3,dp[i-4][j-4]+4,...,dp[i-f[i][j]][j-f[i][j]]+f[i][j])
考虑i-1
dp[i-1][j-1]=max(dp[i-1-3][j-1-3]+3, dp[i-1-4][j-1-4]+3, ... , dp[i-1-f[i-1][j-1]][j-1-f[i-1][j-1]]+f[i-1][j-1])
f[i][j]=f[i-1][j-1]+1 (当 a[i]=b[j]时)
dp[i-1][j-1]=max(dp[i-4][j-4]+3, dp[i-5][j-5]+4, ... ,dp[i-f[i][j]][j-f[i][j]]+f[i][j]-1)
所以dp[i][j]=max(dp[i-3][j-3]+3, dp[i-1][j-1]+1)
然后判断即可
减少DP中找决策的维数(减少循环层数)
这里利用了展开来找max 或min的规律,以达到减少重复寻找断开位置的循环
DP中好像还有也要枚举分开的位置,进行加减转移的最大值,为了减少这个循环层数,可以将所有点分开位置的枚举转移放到最后的那个点一起转移。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <cmath>
using namespace std;
string a,b;
int la,lb;
int f[2100][2100];
int dp[2100][2100][1];
int main()
{
cin>>a;
cin>>b;
la=a.size();
lb=b.size();
a=' '+a;
b=' '+b;
for (int i=1;i<=la;i++)
for (int j=1;j<=lb;j++)
if (a[i]==b[j])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=0;
for (int i=1;i<=la;i++)
for (int j=1;j<=lb;j++)
{
dp[i][j][1]=0;
if (f[i][j]>=3)
{
dp[i][j][1]=dp[i-3][j-3][0]+3;
if (f[i][j]>3)
dp[i][j][1]=max(dp[i][j][1],dp[i-1][j-1][1]+1);
}
dp[i][j][0]=max(dp[i-1][j][0],max(dp[i][j-1][0],dp[i][j][1]));
}
cout<<dp[la][lb][0];
return 0;
}<span style="color:#ff6600;">
</span>