简单线性Dp



线性Dp的定义

处理起来是线性的(???),这部分交给ai老先生解释:



AcWing 898. 数字三角形

题目链接:https://www.acwing.com/activity/content/problem/content/1002/


思路

正序Dp:

  • 状态表示f[i][j]
    • 集合:表示的是到第 i i i行第 j j j列的数的路径和的集合。
    • 属性:Max最大值
  • 状态计算:
    • 我们发现f[i][j]是由上一层的相邻两个数取最大值决定的,那么有方程: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − 1 ] , f [ i − 1 ] [ j ] ) + a [ i ] [ j ] f[i][j] = max(f[i - 1][j - 1], f[i - 1][j]) + a[i][j] f[i][j]=max(f[i1][j1],f[i1][j])+a[i][j]

逆序Dp:

  • 状态计算:
    • 是由下面两个相邻的数的最大值来决定的,倒着算即可。

CODE

正序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, a[N][N];

int main(){
    cin >> n;
    for(int i = 0; i <= n; ++i)
        for(int j = 0; j <= i + 1; ++j)
            a[i][j] = -INF;
    
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            cin >> a[i][j];
            
    for(int i = 2; i <= n; ++i)
        for(int j = 1; j <= i; ++j){
            a[i][j] = a[i][j] + max(a[i - 1][j], a[i - 1][j - 1]);
        }
    int res = -INF;
    for(int j = 1; j <= n; ++j) res = max(res, a[n][j]);
    cout << res << endl;
}

倒序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 520, INF = 0x3f3f3f3f;

int n, a[N][N];

int main(){
    cin >> n;
    for(int i = 1; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            cin >> a[i][j];
            
    for(int i = n - 1; i >= 1; --i)
        for(int j = 1; j <= i; ++j)
            a[i][j] = max(a[i + 1][j], a[i + 1][j + 1]) + a[i][j];
            
    cout << a[1][1] << endl;
}


AcWing 895. 最长上升子序列

题目链接:https://www.acwing.com/activity/content/problem/content/1003/


Dp 分析

  • 状态表示f[i]
    • 集合:在集合 [ 1 , i ] [1, i] [1,i] 之间的最长上升子序列的长度。
    • 属性:Max
  • 状态计算:
    • 可以从哪些状态迁移过来:
      • 在这个区间内的序列长度,即f[k]
    • 每个迁移状态怎么取值:
      • 如果a[i] > a[k]f[i] = f[k] + 1
      • 如果a[i] <= a[k]f[i] = f[k]
      • 最后取最大值即可。

CODE

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1024, INF = 0x3f3f3f3f; // 定义常量N和INF
int s[N]; // 定义数组s,用于存储输入的序列
int f[N]; // 定义数组f,用于存储以每个元素结尾的最长递增子序列的长度

int main()
{
    int n; // 定义变量n,表示序列的长度
    scanf("%d", &n); // 输入序列的长度
    
    for(int i = 1; i <= n; ++i) scanf("%d", &s[i]); // 输入序列的元素
    
    for(int i = 1; i <= n; ++i) f[i] = 1; // 初始化数组f,每个元素的初始值为1
    
    // 动态规划求解最长递增子序列
    for(int i = 2; i <= n; ++i)
        for(int j = 1; j <= i; ++j)
            f[i] = max(f[i], (s[j] < s[i] ? (f[j] + 1) : 1));
           
    int ans = -INF; // 定义变量ans,用于存储最长递增子序列的长度
    for(int i = 1; i <= n; ++i) ans = max(ans, f[i]); // 找出最长递增子序列的长度
    cout << ans << endl; // 输出最长递增子序列的长度
}


AcWing 896. 最长上升子序列 II

题目链接:https://www.acwing.com/activity/content/problem/content/1004/

在这里插入图片描述


分析

数据量太大,如果按照之前 o ( N 2 ) o(N^{2}) o(N2)的复杂度必然超时,所以需要优化。
我们可以发现,如果有一个序列3,4,1 …这种,那么当序列扫到1时,我们可以将目前最长序列3,4中的3替换成1,如果后续有2,我们就把4替换为2… …显然这是最优,因为后面比3大的数必然比1大,但是反之不对,那么这相当于扩充了选择范围,如果我们不替换3,那么2来的时候是不能加入到子序列中的。
比如序列是3,4,1,2,3:显然最大子序列应该是1,2,3,但是如果我们从一开始不替换那子序列就是3,4,是不对的。
如果子序列是3,4,5,1:最长子序列就是3,4,5,但是由于我们的替换就会变成1,4,5,但是这并不影响结果,最长子序列长度没变,只是具体数值错了。
这是一种基于贪心的思想,用更优替换较次选择。


AC CODE

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
int n, a[N], q[N];

int main(){
    scanf("%d", &n);
    
    for(int i = 0; i < n; ++i) scanf("%d", &a[i]);
    
    int len = 0;
    q[0] = -2e9;
    
    for(int i = 0; i < n; ++i){
        int l = 0, r = len; 
        while(l < r){
            int mid = (l + r + 1) >> 1;
            if(a[i] > q[mid]) l = mid;
            else r = mid - 1;
        }
        len = max(len, r + 1);
        q[r + 1] = a[i];
    }
    
    cout << len << endl;
}


AcWing 902. 最短编辑距离

链接:https://www.acwing.com/activity/content/problem/content/1094/
在这里插入图片描述


题解

链接:https://leetcode.cn/problems/edit-distance/solutions/189676/edit-distance-by-ikaruga/
力扣官方题解写的很牛逼了


AC CODE

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;
int f[N][N];
char a[N], b[N];
int n, m;

int main()
{
    scanf("%d%s", &n, a + 1);
    scanf("%d%s", &m, b + 1);
    
    for(int i = 0; i <= n; ++i) f[i][0] = i;
    for(int i = 0; i <= m; ++i) f[0][i] = i;
    
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= m; ++j){
            if(a[i] == b[j]){
                f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
                f[i][j] = min(f[i][j], f[i - 1][j - 1]);
            }else{
                f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
                f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
            }
        }
    }
    
    printf("%d\n", f[n][m]);
}


AcWing 897. 最长公共子序列

题目链接:https://www.acwing.com/activity/content/problem/content/1005/


Dp 分析

  • 状态表示f[i][j]
    • 集合:表示对 A A A 序列的前 i i i 个字母和 B B B 序列的前 j j j 个字母所包含的最大公共子序列长度。
    • 属性:Max
  • 状态计算:
    • 集合划分:第 i i i 个和第 j j j 个字母是否相同。
      • 相同:f[i][j] = f[i - 1][j - 1] + 1
      • 不同:f[i][j] = max(f[i - 1][j], f[i][j - 1])
        • i − 1 i - 1 i1 个字母与前 j j j 个字母的最大公共子序列。
        • i i i 个字母与前 j − 1 j - 1 j1 个字母的最大公共子序列。

CODE

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1024; // 定义常量N
int f[N][N]; 	// 定义二维数组f,用于存储最长公共子序列的长度
char a[N], b[N]; // 定义字符数组a和b,用于存储输入的两个字符串

int main()
{
    int n, m; 	// 定义变量n和m,表示两个字符串的长度
    scanf("%d%d", &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][j]的值
            // 如果a[i]等于b[j],则更新f[i][j]的值
            if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1; 
        }
        
    cout << f[n][m] << endl; // 输出最长公共子序列的长度
}


  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值