个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

线性DP

理解
动态规划是指把一个问题分成若干个子问题,通过局部最优解得到全局最优的一种算法策略或者说一种思想方法,简单来讲就是用一个数组表示我们要求的问题的答案:由前一个问题的答案推出另一个问题的答案。
dp一般有三个步骤:
1、设计状态:设计合适的数组以及相应的含义。
2、状态转移方程:从已知问题的答案推出当前问题的答案。
3、确定边界条件:递推的初值或者说记忆化搜索的回溯条件,以及各个数组的初值。
线性DP问题是指递推方程具有明显的线性关系,有一维线性和二维线性


数字三角形

最长上升子序列

题目描述:给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。
思路:

  • 状态表示:f[i]表示从第一个数字开始算起,以w[i]结尾的最长的上升序列,
  • 状态计算:在w[i] > w[j]时,f[i] = max(f[i], f[j] + 1)
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1100;
int a[N];
int f[N]; // f[i]表示前i个数字的最长上升子序列
int main()
{
    int n;
    cin >> n;
    int res = 0;
    for(int i = 1;i <= n;i++)   scanf("%d",&a[i]);
    for(int i = 1; i <= n;i++)
    {
        f[i] = 1;
        for(int j = 1; j < i;j++)
        {
            if(a[j] < a[i]) f[i] = max(f[i],f[j]+1); // 因为j<i,所以f[j]是更新过的
        }
        res = max(res,f[i]);
    }
    cout << res;
    return 0;
}

最长公共子序列

题目描述:给定两个长度分别为 N 和 M 的字符串 A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。
思路:
注意:子串是连续的,子序列可以不连续

  • 状态表示:设dp[i][j]表示A的前i个字符 和 B的前j个字符 的最长公共子序列长度
  • 状态转移:
    • A[i] == B[j],那么dp[i][j] = dp[i-1][j-1] + 1,前面的最长公共子序列加上最后这一个
    • A[i] != B[j],那么A[i]B[j]肯定不会同时出现在最长公共子序列中,可以分别与A的前 i - 1个和B的前j - 1个字符比较, dp[i][j] = max(dp[i-1][j], dp[i][j-1])
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N][N]; // f[i][j] 表示s1前i个字符和 s2前j个字符公共子序列的最大值
char s1[N], s2[N];
int main()
{
    int m,n;
    cin >> m >> n; // 字符串的长度
    cin >> s1+1 >> s2+1;
   / f[1][0] = f[0][1] = 0;
    for(int i = 1;i <= m;i++)
    {
        for(int j = 1; j <= n;j++)
        {
            if(s1[i] == s2[j])  f[i][j] = f[i-1][j-1]+1;
            else f[i][j] = max(f[i-1][j],f[i][j-1]);
        }
    }
    cout << f[m][n];
    return 0;
}

最短编辑距离

题目描述:
给定两个字符串 A 和 B,现在要将 A 经过若干操作变为 B,可进行的操作有:

删除–将字符串 A 中的某个字符删除。
插入–在字符串 A 的某个位置插入某个字符。
替换–将字符串 A 中的某个字符替换为另一个字符。
现在请你求出,将 A 变为 B 至少需要进行多少次操作。

思路:

  • 状态表示 dp[i][j]
    • 集合:所有把a中的前i个字母变成b中的前j个字母的操作集合
    • 属性:所有操作中操作次数最小的方案的操作数
  • 状态计算以对a中的第i个字母操作不同划分
    对于A中的前i个字母、B中的前j个字母:
    1、添加。dp[i][j-1]表示使得A中的前 i 个字母变成B中的前 j-1 个的操作次数,那么A再添加1个字母就变成B的前 j 个字母了 dp[i][j] = dp[i][j-1]+1
    2、删除。dp[i-1][j]表示使得A中的前 i - 1个字母变成B中的前 j 个字母的操作次数,那么A删掉1个字母就变成B的前 j个字母了 dp[i][j] = dp[i-1][j] + 1
    3、替换。若a[i] != b[j],那么只要使得A中的前i-1个字母变成B中的前j-1个字母, dp[i][j] = dp[i-1][j-1] + 1
    4、什么都不做。当a[i] == b[j], 有dp[i][j] = dp[i-1][j-1]
#include<iostream>
using namespace std;
const int N = 1010;
int d[N][N]; // d[i][j] 集合:所有把a中的前i个字母变成b的前j个字母的操作集合  属性:最小的操作次数
char A[N], B[N];
int main() {
    int n, m;
    cin >> n;
    cin >> A + 1;
    cin >> m;
    cin >> B + 1;
    for(int i = 0; i <= n;i++)  d[i][0] = i; // 删除操作,删除i次
    for(int i = 0; i <= m;i++)  d[0][i] = i; // 添加操作,添加i次
    
    for(int i = 1; i <= n;i++)
        for(int j = 1; j <= m;j++) {
            d[i][j] = min(d[i][j - 1],d[i-1][j]) + 1;
            d[i][j] = min(d[i][j], d[i-1][j-1] + (A[i] != B[j]));
        }
    cout << d[n][m] << endl;
    return 0;
}
posted @ 2022-02-16 15:37  红叶~  阅读(115)  评论(0编辑  收藏  举报