分析:
1.状态表示
f[i][j]:
状态表示: 第一个序列的前i个和第二个序列的前j个, 并且以第二个字符的第j个结点b[j]作为结尾的最长公共上升子序列的值
为什么进行这样表示呢?
1.公共子序列 f[i][j] 第一序列前i个和第二序列前j个. 分为00,01,10,11四个状态
2.而又是求上升子序列。 所以需要一个a[i]或b[i]做为尾巴。进行判断前i - 1个中小于a[i]或b[i]的进行求解最长上升子序列
所以需要加一个附加条件,b[j]或者a[i]作为结尾。
而这个2.可以让我们发现,最长公共子序列不一定非要用1.的方式,
还可以用另一种状态表示方式:
状态表示:f[i][j]. a中前i个与b中前j个进行匹配,且以b[j]结尾的最长公共子序列的长度值。
状态计算:
1.存在b[j],但不要a[i], 所以为f[i][j] = f[i - 1][j]
2.存在b[j],存在a[i],则一定a[i] == b[j].
所以f[i][j] = max(f[i-1][j],1); 至少为1,因为a[i] == b[j]
随后去掉a[i]和b[j],但这个时候与最长方式1的最长公共子序列的a[i - 1][j - 1] + 1 不同,1.的为a的前i -1中和b的前j - 1中。
而我们要以1~j - 1的b[k]结尾
所以for(int k = 1 ; k < j ; k++)
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
通过此题了解到 最长公共子序列的计算方式2:
# include <iostream>
using namespace std;
const int N = 1010;
int f[N][N];
char a[N];
char b[N];
int n1,n2;
int main()
{
cin >> n1 >> n2;
cin >> a + 1 >> b + 1;
f[0][0] = 1;
for(int i = 1 ; i <= n1 ; i++)
{
for(int j = 1 ; j <= n2 ; j++)
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j])
{
f[i][j] = max(f[i][j],1);
for(int k = 1 ; k < j ; k++)
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
}
}
int res = 0;
for(int j = 1 ; j <= n2 ; j++)
{
res = max(res , f[n1][j]);
}
printf("%d\n",res);
return 0;
}
2.状态计算
由于以b[j]作为尾巴
f[i][j]
按照公共子序列的方式分为:a[i]存在,a[i]不存在两种大类。
1.a[i]不作为末尾结点 : 则为f[i - 1][j]
2.a[i]作为末尾结点:(于是接着按照最长上升子序列方式进行拆分,分解成更小的)
由于a[i]和b[j]作为末尾,所以a[i] == b[j]. 然后按照倒数第二个结点值进行分类
可以按照b[j]来进行细分求最长上升子序列
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
if(a[i] == b[j])
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j])
{
for(int k = 1 ; k <= j - 1 ; k++)
{
if(b[k] < b[j])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
}
}
}
}
或者按照a[j]来分都可行
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
if(b[i] == a[j])
{
f[i][j] = f[i - 1][j];
if(b[i] == a[j])
{
for(int k = 1 ; k <= j - 1 ; k++)
{
if(a[k] < a[j])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
}
}
}
}
于是我们按照b[]来进行细分
(1)b[0]:倒数第二个数不存在 f[i][j] = 1;
(2)b[1]:倒数第二个数为b[1] if(b[1] < b[j])时
则f[i][j] = max(f[i][j] , f[i - 1][1] + 1) // 因为a[i] == b[j]可以将a[i]和b[j]先去掉,看前面的序列值,找前i - 1 与 前b[1]匹配的最大值 然后 + 1 (a[i]与b[j]匹配)
(3)b[k]:
则if(b[k] < b[j])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
代码优化
因为目前这样写的话,需要三重循环,会发生TLE
而代码优化就是对代码做等价变形
//朴素做法:三重循环会TLE,后面需要进行优化
for(int i = 1 ; i <= n; i++)
{
for(int j = 1 ; j <= n ; j++)
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j])
{
//后续代码是找当a[i] == b[j]的时候,前a[i - 1],b[j - 1]的最长公共子序列长度。实际与k无关可以进行优化。每次遍历j的时候,看a[i]是否==b[j],如果a[i] != b[j],则可求前a[i - 1]和当前b[j]构成的公共上升子序列的最大长度,f[i - 1][j]
f[i][j] = max(f[i][j],1); //最长公共子序列至少为1
for(int k = 1 ; k <= j - 1 ; k++)
{
if(b[k] < b[j])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
}
}
}
由于此时a[i] == b[j],所以b[j]可以用a[i]代替
for(int k = 1 ; k <= j - 1 ; k++)
{
if(b[k] < a[i])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
而这部分的代码含义就是当a[i] == b[j]时 从前j - 1的子序列中找到最长公共上升子序列的最大长度
而每一次for(k遍历)都进行了重复计算。因为前f[i - 1][]都进行重复多次k的求解
可以用一个如同前缀和一般的思路,当i不变的时候,前面求过的f[i - 1][k]不会改变。
而且是求最大值,
所以使用一个全局变量代替已经求过的最大值即可。
每次 j 改变,就看看这个改变后的j也就相当于这里面的k所对应的值是否还是最大值
1.a[i] == b[j]则代表以a[i] 和 b[j]看作结尾
2.a[i] > b[j]则求其长度,用于后面当a[i] == b[j1]用 (j1 > j)。
代码实现:
# include <iostream>
using namespace std;
const int N = 3010;
int a[N];
int b[N];
int f[N][N];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1 ; i <= n ; i++)
{
scanf("%d",&a[i]);
}
for(int i = 1 ; i <= n ; i++)
{
scanf("%d",&b[i]);
}
/* //朴素做法:三重循环会TLE,后面需要进行优化
for(int i = 1 ; i <= n; i++)
{
for(int j = 1 ; j <= n ; j++)
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j])
{
//后续代码是找当a[i] == b[j]的时候,前a[i - 1],b[j - 1]的最长公共子序列长度。实际与k无关可以进行优化。每次遍历j的时候,看a[i]是否==b[j],如果a[i] != b[j],则可求前a[i - 1]和当前b[j]构成的公共上升子序列的最大长度,f[i - 1][j]
f[i][j] = max(f[i][j],1); //最长公共子序列至少为1
for(int k = 1 ; k <= j - 1 ; k++)
{
if(b[k] < b[j])
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
}
}
}
} */
/*
代码优化就是对代码做等价变形
*/
for(int i = 1 ; i <= n ; i++)
{
int maxv = 0; //maxv用来存a[i] == b[j]时,前b[j - 1]个的最长公共子序列,当j == 1时,则maxv的开头为0
for(int j = 1 ; j <= n ; j++)
{
f[i][j] = f[i - 1][j];
if(a[i] == b[j]) //当a[i] == b[j]的时候,则把a[i],b[j]作为两个字符串的结尾,找前j - 1这字符的最大长度公共上升子序列
{
f[i][j] = max(f[i][j],maxv + 1); //maxv存的就是前b[j - 1]个时候的最长公共上升子序列 然后 + 1(a[i]和b[j]作为尾巴的组合)
}
if(b[j] < a[i]) // 当a[i] > b[j]的时候,则求出此b[j]下的最长公共上升子序列的长
{
maxv = max(maxv,f[i - 1][j]);
/*
也就是
当b[j + 1] == a[i]时,求maxv.
等价于:
if(b[k] < b[j]) //因为b[j] == a[i]
{
f[i][j] = max(f[i][j],f[i - 1][k] + 1);
}
而优化后减去了对k的遍历。
*/
}
}
}
/*
最长上升子序列按照 a[]来细分时
for(int i = 1 ; i <= n ; i++)
{
int maxv = 0;
for(int j = 1 ; j <= n ; j++)
{
f[i][j] = f[i - 1][j];
if(b[i] == a[j])
{
f[i][j] = max(f[i][j],maxv + 1);
}
if(a[j] < b[i])
{
maxv = max(maxv,f[i - 1][j]);
}
}
}
*/
int res = 0;
//f[i][j]是前a[i]个和前b[j]个并且以b[j]结尾 。 也可能最大的不在n以b[n]结尾处
for(int i = 1 ; i <= n ; i++)
{
res = max(res , f[n][i]);
}
printf("%d\n",res);
return 0;
}