最长公共子序列
例:求两个字符串最长公共子序列长度。
如a[] = {"abcedf"}, b[] = {"abtrenf},则最长公共子序列为abef,长度为4
伪代码:
1 for(i...a[]的长度) 2 for(j...b[]的长度) 3 { 4 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); 5 if(a[i] == b[j]) 6 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); 7 } 8 输出dp[a[]的长度][b[]的长度]
代码
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 using namespace std; 6 const int maxn = 250; 7 int dp[maxn][maxn]; 8 char a[maxn], b[maxn]; 9 int main() 10 { 11 scanf("%s%s", a + 1, b + 1); 12 /*a + 1意思是从a[1]开始读入,而并非从a[0],这么做是 13 为了后面dp时dp[i][j - 1]不越界 。但相应的后面循环就 14 要从 i = 1到 i = la,而不是到 i = la - 1*/ 15 int la = strlen(a + 1), lb = strlen(b + 1); 16 for(int i = 1; i <= la; ++i) 17 { 18 for(int j = 1; j <= lb; ++j) 19 { 20 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); 21 if(a[i] == b[j]) 22 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); 23 } 24 } 25 printf("%d\n", dp[la][lb]); 26 return 0; 27 }
这是子序列的一个最基本的问题,从这个问题可以衍生出很多相关的子序列问题。
最长回文子序列
例:有一个字符串,求最少删去几个字符,使它成为一个回文字符串。
思路:转化为求最长公共子序列问题:将这个字符串倒序存入另一个字符串,求这两个字符串的最长公共子序列
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<iostream> 5 using namespace std; 6 const int maxn = 250; 7 int n ,dp[maxn][maxn]; 8 char a[maxn], b[maxn]; 9 int main() 10 { 11 scanf("%d", &n); 12 scanf("%s", a + 1); 13 for(int i = 1; i <= n; ++i) 14 b[i] = a[n - i + 1]; 15 for(int i = 1; i <= n; ++i) 16 { 17 for(int j = 1; j <= n; ++j) 18 { 19 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); 20 if(a[i] == b[j]) 21 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); 22 } 23 } 24 printf("%d\n", dp[n][n]); 25 return 0; 26 }
最长单调子序列
例:有一个整形数组,求他的最长递增(递减)子序列。
思路:转化为求最长公共子序列问题:将这个数组排序,存到另一个数组中,求这两个数组的最长公共子序列。
这个方法空间复杂度为O(2n),可以改进为O(n)。
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #include<algorithm> 5 using namespace std; 6 const int maxn = 10005; 7 int a[maxn], b[maxn]; 8 int dp[maxn][maxn], n; 9 int main() 10 { 11 scanf("%d", &n); 12 for(int i = 1; i <= n; ++i) 13 { 14 scanf("%d", &a[i]); 15 b[i] = a[i]; 16 } 17 sort(b + 1, b + n + 1); 18 for(int i = 1; i <= n; ++i) 19 { 20 for(int j = 1; j <= n; ++j) 21 { 22 dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); 23 if(a[i] == b[j]) 24 dp[i][j] = max(dp[i][j], dp[i - 1][j - 1] + 1); 25 } 26 } 27 printf("%d\n", dp[n][n]); 28 return 0; 29 }
改进:设dp[i]表示以a[i]为终点的最长上升子序列的长度,则当a[j] < a[i],且j < i时,dp[i] = dp[j] + 1.
所以dp方程:
dp[i] = max(dp[i], dp[j] + 1)