前言:
本系列是看的B站董晓老师所讲的知识点做的笔记
董晓算法的个人空间-董晓算法个人主页-哔哩哔哩视频 (bilibili.com)
树塔-记忆化搜索
特点(前提):从上向下的累加和是不能重复使用的,从下向上的累加和是可以重复使用的
把题目变成二叉树的形式:4的左子树分别是下一行的8和下一行右边的3,依次类推,每一个树的左子树都是他的下一行的数和下一行数的右边的那个数
int a[9][9] =
{ {1},{4,6},{8,3,9},{5,7,2,1}};int n = 4;int f[9][9];//记录从上向下的累加和int dfs(int x, int y){if (f[x][y] != 0) return f[x][y];//说明该点已经遍历过if (x == n - 1) f[x][y] = a[x][y];//说明已经全部遍历完了elsef[x][y] = a[x][y] + max(dfs(x + 1, y), dfs(x + 1, y + 1));return f[x][y];}
线性DP
数塔
int calu(int x, int y)
{int x, y;for (x = n - 2; x >= 0; x--)for (y = 0; y <= x; y++)//这样就可以弄成塔的形式a[x][y] += max(a[x + 1][y], a[x + 1][y + 1]);cout << "max=" << a[x][y];
}
如果需要输出路径的话,需要有一个前驱路径数组p[x][y]和一个备份数组b[x][y];
前驱路径数组主要是记录y的增值的,把两种情况(a[x + 1][y]>/<=a[x + 1][y + 1])分别设为0和1,r然后在通过遍历数组b[x][y],y=y+p[x][y]进行输出
最长上升子序列
B3637 最长上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
动态规划(O(n²))
for (int i = 1; i <= n; i++) f[i] = 1;//把初始化化长度都设为1for (int i = 2; i <= n; i++){for (int j = 1; j < i; j++)if (a[i] > a[j]) f[i] = max(f[j] + 1,f[i]);//分别依次计算不同下标时候的长度for (int i = 1; i <= n; i++) ans = max(ans, f[i]);//遍历寻找长度最长的长度}
二分查找
思想:
新进来一个元素a[i]:
(1)大则添加:如果a[i]大于b[len],直接让b[++len]=a[i]。即b数组的长度增加1,而且添加了一个元素。
(2)小则替换:如果a[i]小于或等于b[len],就用a[i]替换掉b数组中第一个大于或等于a[i]的元素。
假设第一个大于a[i]的元素是b[j],那么用a[i]换掉b[j]后,会使得b[1...j]这个上升子序列的结尾元素更小。对于一个上升子序列,其结尾元素越小,越有利于续接其它元素,也就越可能变得更长。
注意:
b数组不是存储的最长上升子序列,但是子序列的长度相同
核心代码
二分查找第一个大于等于x的位置
int find(int x) {int l = 1, r = len, mid;while (l <= r) {int mid = l + r >> 1;if (x > b[mid]) l = mid + 1;else r = mid - 1;}return l;
}
新增元素代码
for (int i = 0; i < n; i++) {if (b[len] < a[i]) b[++len] = a[i];else b[find(a[i])] = a[i];}
全部代码
#include<iostream>
using namespace std;
const int N = 10010;
int n, a[N], b[N], len;
int find(int x) {int l = 1, r = len, mid;while (l <= r) {int mid = l + r >> 1;if (x > b[mid]) l = mid + 1;else r = mid - 1;}return l;
}
int main() {scanf("%d", &n);for (int i = 0; i < n; i++) scanf("%d", &a[i]);b[0] = -2e9;for (int i = 0; i < n; i++) {if (b[len] < a[i]) b[++len] = a[i];else b[find(a[i])] = a[i];}printf("%d\n", len);return 0;
}
最长公共子序列
1.最长公共子序列不是连续的一段区间
2.记录路径的时候前驱数组可以去掉
1.思路
2.核心代码
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 - 1] + 1;else f[i][j] = max(f[i - 1][j - 1], max(f[i - 1][j], f[i][j - 1]));
3.题目一
P1439 【模板】最长公共子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
前提:
两个排列都是1到n的排列,说明元素都是相同的只是顺序不同,
思路:
把LCS转换成LIS
原因:
通过离散化可以得到一个性质。
离散化步骤:
A:3 2 1 4 5
B:1 2 3 4 5
重新把A,B数组中的元素替换掉,使得A数组是其次递增的
标个号:把3标成a,把2标成b,把1标成c.…于是变成:
A: a b c d e
B: c b a d e
结论:最长公共子串的长度不会改变,又因为A数组是递增的,所以说在B数组中递增的子序列就是A的子序列
离散化代码:
for (int i = 1; i <= n; i++){cin >> m;line[m] = i;}
最长公共子串
核心代码
for(int i=1; i<=strlen(a); i++){for(int j=1; j<=strlen(b); j++){if(a[i-1]==b[j-1]) f[i][j]=f[i-1][j-1]+1;else f[i][j]=0;if(f[i][j]>max)max=f[i][j];}
编辑距离
题目
P2758 编辑距离 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
二维思路
一维思路
具体分析
1.初始化,看上面的矩阵,第一行的意思是“LOVER"此时是空串,则“NOTV”那里有几个字符就需要删除几个字符
for(int i=1;i<=la;i++) f[i][0]=i;for(int i=1;i<=lb;i++) f[0][i]=i;
2. 一维思路如果不清楚的话,就对照着图片上方的四个格子推一次
总结:
1.LIS:Longest Increasing Subsequence 最长递增子序列
LCS:Longest Common Subsequence 最长公共子序列
2.公共子串:字符必须是连续相等的;
公共子序列:字符必须是相等的,可以不连续。