线形DP
AcWing 898. 数字三角形

将想要计算的点的状态分为两部分,从左上角、右上角两部分走过来。求最大值:
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 510, INF= 1e9;
int a[N][N], f[N][N];
int n;
int main(){
cin >> n;
memset(f, -0x3f, sizeof f);
for(int i = 1; i<=n; i++){
for(int j = 1; j<=i; j++) cin >> a[i][j];
}
f[0][0] = 0;
for(int i = 1; i<=n; i++){
for(int j = 1; j<=i; j++){
f[i][j] = max(f[i-1][j-1]+a[i][j], f[i-1][j]+a[i][j]);
}
}
int res = -INF;
for(int i =1 ; i<=n; i++){
// cout << "------1" << endl;
res = max(res, f[n][i]);
}
cout << res;
return 0;
}
AcWing 895. 最长上升子序列

需要计算点是由什么状态到达的, 该点的前面状态是小于这个点的位置。可以用数组g来记录路径,f[i] = f[j] + 1; g[i] = j; 存储该节点的上一个节点坐标

cpp
#include <bits/stdc++.h>
using namespace std;
const int N= 1010;
int a[N], f[N], g[N];
int n;
int main(){
cin >> n;
for(int i = 1; i<=n; i++) cin >> a[i];
for(int i = 1; i<=n; i++){
f[i] = 1;//最小的长度为1;
g[i] = 0;
for(int j = 1; j<i; j++){
if(a[j] < a[i]){
if(f[i] < f[j]+1){
f[i] = f[j] + 1;
g[i] = j;
}
}
}
}
int res = 0;
int k = 0;
for(int i =1; i<=n; i++)
{
res = max(res, f[i]);
k = i;
}
cout << res << endl;
for(int i = 0, len = f[k]; i<len; i++){
cout << a[k] << " ";
k = g[k];
}
return 0;
}
896. 最长上升子序列 II

用p 数组来存储结果,遍历输入的数组与p数组的结尾比较大的就进入,当a[i] <= f[cnt] 的时,我们就用a[i]去替代数组 p 中的第一个**大于等于a[i]**的数
我们用a[i]去替代f[i]的含义是:以a[i]为最后一个数的严格单调递增序列,这个序列中数的个数为l个。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N], q[N];
int n;
int cnt;
int find(int x){
int l = 0, r= cnt;
while(l < r){
int mid = l + r >> 1;
if(q[mid] >= x) r = mid;
else l = mid + 1;
}
return r;
}
int main(){
cin >> n;
for(int i = 1; i<=n; i++) cin >> a[i];
memset(q, -0x3f, sizeof q);
for(int i = 1; i<=n; i++){
if(a[i] > q[cnt]){
q[++cnt] = a[i];
}else {
q[find(a[i])] = a[i];
}
}
cout << cnt;
return 0;
}
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int a[N], q[N];
int n;
int main(){
cin >> n ;
for(int i =1; i<=n; i++) cin >> 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, l+1);
q[l+1] = a[i];
}
cout << len;
return 0;
}
897. 最长公共子序列

状态转移:f [ i ][ j ] 是由当前位置的字符是否相等来判断的, 如果相等就可以直接转移到 f[i-1][j-1] +1。不相等的话,可以对**f[i-1][j] (不包含a[i]的最大值),f[i][j-1]**两种状态取max来转移。

cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N];
int n, m;
int main(){
cin >> n >> m;
cin >> a+1 >> b+1;
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
f[i][j] = max(f[i-1][j], f[i][j-1]);//这种一定小于f[i-1][j-1] + 1可以先求出
if(a[i] == b[j]) f[i][j] = max(f[i-1][j-1] + 1, f[i][j]);
}
}
cout << f[n][m];
return 0;
}
902. 最短编辑距离

状态方程:f[i][j]代表前 a的前i个字符和b的前j个字符相同最少要操作多少次
f[ i ][ j ] =min({
// f [ i - 1][ j ] + 1, // 删除 a[i](删之前i-1个 = j个)
// f [ i ][ j-1 ] + 1, // 在 a[i] 后插入 b[j](添加之前前i个等于j-1个)
// f[ i-1 ][ j-1 ] + (a[ i ] != b[ j ]) // 替换:不同就+1,相同就+0
// });
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char a[N], b[N];
int f[N][N];
int n, m;
int main(){
cin >> n >> a+1;
cin >> m >> b+1;
// //f[i][j] = min(f[i][j], f[i-1][j-1]);最小值不能直接先比
// memset(f, 0x3f, sizeof f);
//f[i][j]代表前 a的前i个字符和b的前j个字符最少要操作多少次
for(int i = 1; i<=m; i++) f[0][i] = i; //表示 a 有0个字符,b有i个需要增加i次
for(int i = 1; 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-1][j] + 1, // 删除 a[i]
// f[i][j-1] + 1, // 在 a[i] 后插入 b[j]
// f[i-1][j-1] + (a[i] != b[j]) // 替换:不同就+1,相同就+0
// });
f[i][j] = min(f[i-1][j] + 1, 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);
}
}
}
cout << f[n][m];
}
AcWing 899. 编辑距离

cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
char str[N][N];
int f[N][N];
int n, m;
int edit_distance(char a[N], char b[N]){
int la = strlen(a+1), lb = strlen(b+1);
for(int i=1; i<=lb; i++) f[0][i] = i;
for(int i=1; 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] + 1, f[i][j-1] + 1, f[i-1][j-1]+(a[i] != b[j])});
}
}
return f[la][lb];
}
int main(){
cin >> n >> m;
for(int i = 1; i<=n; i++) cin >> str[i] + 1;
while(m--){
char s[N];
int limit;
cin >> s+1 >> limit;
int res =0 ;
for(int i =1 ; i<=n; i++){
if(edit_distance(str[i], s) <=limit) res++;
}
cout << res << endl;
}
return 0;
}
区间DP
282. 石子合并

状态转移方程中的集合的表达 :f[l][r] = 把 l ~ r 这一段石子合并成一堆的最小代价。
从小大大列举区间 (len = 2; len<=n; len++)再枚举起点(i = 1; i+len-1<=n; i++)能枚举出所有区间为二的状态;for(int k = l; k<r; k++){ **f[l][r] = min(f[l][r], f[l][k] + f[k+1][r]+s[r] - s[l-1]);**并且能比较出区间怎么配分状态最小。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int f[N][N], s[N];
int n;
int main(){
cin >> n;
for(int i = 1; i<=n; i++) cin >> s[i];
for(int i = 1; i<=n; i++) s[i] += s[i-1];
// memset(f, 0x3f, sizeof f);
for(int len = 2; len<=n; len++){//这个区间要一直枚举到最大
for(int i = 1; i+len-1<=n; i++){//枚举起点 不能 i<n;整段都有起点
int l = i, r = i+len-1;
//区间分为两半,计算每个区间求最小值
f[l][r] = 1e9;
for(int k = l; k<r; k++){ //区间最小 l-1
f[l][r] = min(f[l][r], f[l][k] + f[k+1][r]+s[r] - s[l-1]);
}
}
}
cout << f[1][n];
return 0;
}