动态规划之lcs(最长公共子序列)与lis(最长递增子序列)
目录
动态规划之lcs(最长公共子序列)与lis(最长递增子序列)
子序列:
由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
最长公共子序列
dp[i][j]:对于长度为[1,i]的字符串a和长度为[1,j]的字符串b的最长公共子序列长度为dp[i][j]
状态转移方程如下:
if (text1[i - 1] == text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
for (int i = 1; i <= lena; i++) {
for (int ig = 1; ig <= lenb; ig++) {
///*或
dp[i][ig] = std::max(dp[i - 1][ig], dp[i][ig - 1]);
if (a[i] == b[ig]) {
dp[i][ig] = std::max(dp[i][ig], dp[i - 1][ig - 1] + 1);
}
//
if (a[i] == b[ig]) {
dp[i][ig] = dp[i - 1][ig - 1] + 1;
} else {
dp[i][ig] = std::max(dp[i - 1][ig], dp[i][ig - 1]);
}
}
}
最长递增子序列
dp[i]:i之前包括i的以arr[i]结尾的最长递增子序列的长度
状态转移方程:
if (arr[i] > arr[j]) { //后一个大于前一个
dp[i] = std::max(dp[i], dp[j] + 1);
}
for (int i = 1; i < len; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > result) result = dp[i]; // 取长的子序列
}
例题:
最长公共子序列:【模板】最长公共子序列 - 洛谷
题目描述
给出 1,2,…,n1,2,…,n 的两个排列 P1P1 和 P2P2 ,求它们的最长公共子序列。
输入格式
第一行是一个数 nn。
接下来两行,每行为 nn 个数,为自然数 1,2,…,n1,2,…,n 的一个排列。
输出格式
一个数,即最长公共子序列的长度。
输入输出样例
输入 #1
5
3 2 1 4 5
1 2 3 4 5输出 #1
3
说明/提示
对于 50%50% 的数据, n≤103n≤103;
对于 100%100% 的数据, n≤105n≤105。
用朴素的dp代码:
#include<iostream>
int a[100006], b[100006], dp[10006][10006] = {0};
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]);
}
for (int i = 1; i <= n; i++) {
for (int ig = 1; ig <= n; ig++) {
if (a[i] == b[ig]) {
dp[i][ig] = dp[i - 1][ig - 1] + 1;
} else {
dp[i][ig] = std::max(dp[i - 1][ig], dp[i][ig - 1]);
}
}
}
printf("%d", dp[n][n]);
}
但该题数据量较大,时间超时,空间复杂度巨大
于是将求lcs(最长公共子序列)问题转换为求lis(最长递增子序列)问题进行优化
#include<stdio.h>
int a[100006], b[100006],dp[100006];
int map[100006];
#include<iostream>
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
map[a[i]] = i; //map用来给a[1]定顺序(就是编号),帮助下一步判断大小
dp[i] = 1; //dp数组初始化
}
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
b[i] = map[x];
}
int max=0;
for (int i = 2; i <= n; i++) {
for (int li = 1; li <= i; li++) { //li是从前到后或是从后到前都一样
if (b[i] > b[li]) { //后一个大于前一个
dp[i] = std::max(dp[i], dp[li] + 1);
}
}
if(dp[i]>max){
max=dp[i];
}
}
printf("%d", max);
}
但仍有错误,且时间复杂度不够小
用二分查找,优化时间复杂度
#include<stdio.h>
int a[100006], b[100006], dp[100006];
int map[100006], amp[100006];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
map[a[i]] = i; //map用来给a[1]定顺序(就是编号),帮助下一步判断大小
}
for (int i = 1; i <= n; i++) {
scanf("%d", &b[i]);
}
int result = 1;
dp[1] = map[b[1]];
for (int i = 2; i <= n; i++) {//遍历每一个数
int left = 1, right = result;
while (left <= right) {//二分查找,在map数组里面查找
int mid = (left + right) / 2;
if (dp[mid] <= map[b[i]]) { //要查找的大于中间值
left = mid + 1;
} else {//小于中间值
right = mid - 1;
}
}
dp[left] = map[b[i]];
if (left > result) {
result = left;
}
}
printf("%d", result);
}
合唱队形:[NOIP2004 提高组] 合唱队形 - 洛谷
题目描述
nn 位同学站成一排,音乐老师要请其中的 n−kn−k 位同学出列,使得剩下的 kk 位同学排成合唱队形。
合唱队形是指这样的一种队形:设 kk 位同学从左到右依次编号为 1,2,1,2, … ,k,k,他们的身高分别为 t1,t2,t1,t2, … ,tk,tk,则他们的身高满足 t1<⋯<ti>ti+1>t1<⋯<ti>ti+1> … >tk(1≤i≤k)>tk(1≤i≤k)。
你的任务是,已知所有 nn 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。
输入格式
共二行。
第一行是一个整数 nn(2≤n≤1002≤n≤100),表示同学的总数。
第二行有 nn 个整数,用空格分隔,第 ii 个整数 titi(130≤ti≤230130≤ti≤230)是第 ii 位同学的身高(厘米)。
输出格式
一个整数,最少需要几位同学出列。
输入输出样例
输入 #1
8
186 186 150 200 160 130 197 220输出 #1
4
说明/提示
对于 50%50% 的数据,保证有 n≤20n≤20。
对于全部的数据,保证有 n≤100n≤100。
思路:
题目要将一个数组中的几个元素删除,使得数组呈现先增后减的趋势,要求最少删除的元素的个数,假如只是删除元素使得数组递增,那么只需要找到这个数组中最长递增子序列,将这个序列外的数组元素删除,这是就是最少删除的元素,如果从左边到右边找最长递增子序列和从右边到左边找最长递增子序列 的dp都求出来,然后将两个dp数组里面的数相加,取数组里面的最大值,这个最大值就是这个arr数组先增后降的长度最长的子序列的长度
#include<stdio.h>
#include<string.h>
#include<iostream>
int main() {
int n, arr[101];
scanf("%d", &n);
int left[101], right[101]; //left[i]和right[i]分别存储从最左边到i的最长递增个数和从最右边到i的最长递增个数
for (int i = 1; i <= n; i++) {
scanf("%d", &arr[i]);
left[i] = 1, right[i] = 1;
}
for (int i = 2; i <= n; i++) {
for (int j = i-1; j>=1; j--) {//逆序
if (arr[i] > arr[j]) {
left[i] = std::max(left[i], left[j] + 1);
}
}
}
/*或
for (int i = 2; i <= n; i++) {
for (int j = 1; j<=i; j++) {
if (arr[i] > arr[j]) {
left[i] = std::max(left[i], left[j] + 1);
}
}
}
*/
for (int i = n - 1; i >= 1; i--) {
for (int j = i + 1; j <= n; j++) {
if (arr[i] > arr[j]) {
right[i] = std::max(right[i], right[j] + 1);
}
}
}
/*或
for (int i = n - 1; i >= 1; i--) {
for (int j = n; j >=i; j--) {
if (arr[i] > arr[j]) {
right[i] = std::max(right[i], right[j] + 1);
}
}
}
*/
int max = 0;
for (int i = 1; i <= n; i++) {
max = std::max(left[i] + right[i] - 1, max) ; //重合减去1
}
printf("%d", n-max);
}