目录
- 引言
- 一、最长上升子序列II
- 二、最短编辑距离
- 三、编辑距离
引言
本章内容实际上是对线性DP的一个扩展内容,线性DP主要是DP的一个分类,就是它的状态转移方程是线性的,就是一维一维的,其实我也没理解,人家就是这样说的,反正这种问题就是见题了,见一题做一题,理解一题,会做一种类型题,再难的一般是见不到的,所以就好好刷题就行了,还是记忆力和毅力,加油!
一、最长上升子序列II
标签:贪心
思路:
这道题虽然是从DP中出来的,但其实是一道贪心题,按原来的做法已经超时了 N ( l o g N ) N(logN) N(logN) ,所以得用另一种方法来优化。核心思想:两个长度相同的子序列,肯定是结尾元素小的适用性范围更广更好,所以对于每个长度我们定义一个数组 q [ i ] q[i] q[i] ,表示长度为i的结尾元素为 q [ i ] q[i] q[i] ,这样每次遍历的时候,只需要找到最长且刚好小于 a [ i ] a[i] a[i] 的子序列,并且结尾元素最小,而这样的数组 q q q 必然是一个严格单调的数组,所以可以用二分来找。
题目描述:
给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。输入格式
第一行包含整数 N。
第二行包含 N 个整数,表示完整序列。输出格式
输出一个整数,表示最大长度。数据范围
1≤N≤100000,−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
示例代码:
#include <cstdio>
#include <iostream>using namespace std;const int N = 1e5+10;int n;
int a[N];
int q[N]; //q[i]代表长度为i的上升子序列结尾最小的元素是q[i]int main()
{scanf("%d", &n);for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);int len = 0;q[0] = -2e9;for(int i = 1; i <= n; ++i){int l = 0, r = len;while(l < r){int mid = l + r + 1>> 1;if(q[mid] < a[i]) l = mid;else r = mid - 1;}len = max(len, r+1);q[r+1] = a[i];}printf("%d\n", len);return 0;
}
二、最短编辑距离
标签:DP
思路:
这道题首先得定义好集合f[i][j]代表从A的前i个字符变化到B的前j个字符的最小操作,然后就是有三种操作: 增: f [ i ] [ j ] = f [ i ] [ j − 1 ] + 1 增:f[i][j] = f[i][j-1] + 1 增:f[i][j]=f[i][j−1]+1 删: f [ i ] [ j ] = f [ i − 1 ] [ j ] + 1 删:f[i][j] = f[i-1][j] + 1 删:f[i][j]=f[i−1][j]+1 改: f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] + ( a [ i ] ≠ b [ j ] ) 改:f[i][j] = f[i-1][j-1] + (a[i] \neq b[j]) 改:f[i][j]=f[i−1][j−1]+(a[i]=b[j])然后对这三种操作取最小就行,再在开始的时候初始化 f [ 0 ] [ j ] = j , f [ i ] [ 0 ] = i f[0][j] = j, f[i][0] = i f[0][j]=j,f[i][0]=i
题目描述:
给定两个字符串 A 和 B,现在要将 A经过若干操作变为 B,可进行的操作有:
删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。输入格式
第一行包含整数 n,表示字符串 A 的长度。
第二行包含一个长度为 n 的字符串 A。
第三行包含整数 m,表示字符串 B 的长度。
第四行包含一个长度为 m 的字符串 B。字符串中均只包含大小写字母。输出格式
输出一个整数,表示最少操作次数。数据范围
1≤n,m≤1000
输入样例:
10
AGTCTGACGC
11
AGTAAGTAGGC
输出样例:
4
示例代码:
#include <cstdio>
#include <iostream>using namespace std;const int N = 1010;int n, m;
char a[N], b[N];
int f[N][N];int main()
{scanf("%d%s", &n, a+1);scanf("%d%s", &m, b+1);for(int i = 0; i <= m; ++i) f[0][i] = i;for(int i = 0; i <= n; ++i) f[i][0] = i;for(int i = 1; i <= n; ++i){for(int j = 1; j <= m; ++j){f[i][j] = min(f[i][j-1], f[i-1][j]) + 1;if(a[i] == b[j]) f[i][j] = min(f[i][j], f[i-1][j-1]);else f[i][j] = min(f[i][j], f[i-1][j-1] + 1);}}printf("%d\n", f[n][m]);return 0;
}
三、编辑距离
标签:DP
思路:
这道题和最短编辑距离是一样的,只是现在循环遍历了而已,最输入输出操作好基本没啥问题。
题目描述:
给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。对于每次询问,请你求出给定的 n 个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。每个对字符串进行的单个字符的插入、删除或替换算作一次操作。输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含一个字符串,表示给定的字符串。
再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。字符串中只包含小写字母,且长度均不超过 10。输出格式
输出共 m 行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。数据范围
1≤n,m≤1000,输入样例:
3 2
abc
acd
bcd
ab 1
acbd 2
输出样例:
1
3
示例代码:
#include <cstdio>
#include <cstring>
#include <iostream>using namespace std;const int N = 1010;int n, m;
char str[N][N];
int f[N][N];int edit_distance(char a[], char b[])
{int la = strlen(a+1), lb = strlen(b+1);for(int i = 0; i <= lb; ++i) f[0][i] = i;for(int i = 0; i <= la; ++i) f[i][0] = i;for(int i = 1; i <= la; ++i){for(int j = 1; j <= lb; ++j){f[i][j] = min(f[i-1][j], f[i][j-1]) + 1;if(a[i] == b[j]) f[i][j] = min(f[i][j], f[i-1][j-1]);else f[i][j] = min(f[i][j], f[i-1][j-1] + 1);}}return f[la][lb];
}int main()
{scanf("%d%d", &n, &m);for(int i = 1; i <= n; ++i) scanf("%s", str[i]+1);while(m--){char s[N];int limit;scanf("%s%d", s+1, &limit);int res = 0;for(int i = 1; i <= n; ++i){if(edit_distance(str[i], s) <= limit) res++;}printf("%d\n", res);}return 0;
}