线性dp.

news2024/9/23 6:31:08

线性dp,在进行动态规划中,常以线性的形式表现出来。

我们仍用闫氏dp法来进行求解即可
一、状态表示:当前的状态所代表的含义以及能用几维的形式表现出来。包括①集合,②属性
二、状态计算:如何一步一步的将状态计算出来。一般采用画图法,将整个状态划分成若干状态

题目:898. 数字三角形 - AcWing题库

 思路

用闫氏dp法来分析本道题目
一、状态表示:f[ i ][ j ]。①集合:一条只能用左上方或者右上方移动到底层的一条路径。②属性:所有路径中数字和的最大值。
二、状态计算:将状态划分成两种:从左上方或者右上方走到f[ i ][ j ]。
那么就很简单了,我们将f[ i ][ j ]单拎出来,那么到达f[ i ][ j ]只有两种路径,Ⅰ从f[ i ][ j ]的左上方,Ⅱ从f[ i ][ j ]的右上方,在这两种路径中取一个最大值即可。
:f[ i ][ j ] = max(f[ i - 1 ][ j - 1 ] + a[ i ][ j ], f[ i - 1 ][ j ] + a[ i ][ j ])

代码:

代码一,自上向底枚举

/*
走到f(i,j)有两种情况,即从(i,j)的左上或者右上来抵达f(i, j),两者取一个max。
*/

#include<bits/stdc++.h>

using namespace std;

const int N = 520;

int n;
int f[N][N], a[N][N];

int main()
{
    cin >> n;
    //防止边界问题,从1开始赋值
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= i; j ++ )
            cin >> a[i][j];
            
    memset(f, -0x3f3f3f3f, sizeof f);
    
    //赋初值,第一个状态已经知晓
    f[1][1] = a[1][1];
    //所以从2开始遍历
    for(int i = 2; 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 = -0x3f3f3f3f;
    //对已得出结果的最后一行取一个max,即是题目所要求
    for(int i = 1; i <= n; i ++ )   res = max(res,f[n][i]);
        
    cout << res;
}

对 f 数组进行初始化为- ∞原因是因为,对于边界的f[ i ][ j ] 会造成不是本区域的值(图1中的红色部分)相加的情况,所以要舍去 


代码二,自底向上(图二)

/*
走到f(i,j)有两种情况,即从(i,j)的左上或者右上来抵达f(i, j),两者取一个max。
*/

#include<bits/stdc++.h>

using namespace std;


const int N = 520;

int n;
int f[N][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 = 1; i <= n; i ++ )   f[n][i] = a[n][i];

    //从倒数第二列开始,即不需要初始化f,因为接下来的集合再无其余区域的元素
    for(int i = n - 1; i >= 1; i -- )
        for(int j = i; j >= 1; j -- )
            f[i][j] = max(f[i + 1][j + 1] + a[i][j], f[i + 1][j] + a[i][j]);//转移方程也会有所改变
            
    cout << f[1][1];
}

题目:AcWing 895. 最长上升子序列 - AcWing

 思路:

仍用闫氏dp法进行求解
一、状态表示f[i]。①集合:所有以第i个数结尾的上升子序列。②属性:某一个以i结尾的上升子序列的max
二、状态计算:上升子序列都是以a[i]结尾,我们以第a[i - 1]个数来进行分类(因为序列是严格递增的),查看前一位即可。
如果第i - 1个数为空,即没有第i - 1个数,那么这个序列长度为1(只有i)。如果第i - 1个数为a[1],那么该长度为:以a[1]结尾的子序列的长度+1。
如果第i - 1个数为a2,那么该子序列的长度为:以a[2]结尾的子序列的长度+ 1...
如果第i - 1个数为a[i - 1],那么该子序列的长度为:以a[i - 1]结尾的子序列的长度+ 1。
当然,只有当前一位小于a[i]时,该状态转移才有意义,在转移长度里取一个最大值即可。
最后在已经确定了的序列中取一个最大值即可

 代码:
 

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

using namespace std;

const int N = 1010;
int f[N], a[N];

int main()
{
    int n;
    cin >> n;
    
    for(int i = 1; i <= n; i ++ )   cin >> a[i];
    
    
    for(int i = 1; i <= n; i ++ )
    {
        f[i] = 1;//每个子序列的状态中都至少含有一个数
        for(int j = 1; j <= i; j ++ )
        //只有当a[i] > a[j]时,将a[i]放在以a[j]结尾的子序列后面才会满足严格递增的题目要求
            if(a[j] < a[i])
                f[i] = max(f[i], f[j] + 1);
    }
    
    int res = -0x3f3f3f3f;
    //取一个最大值
    for(int i = 1; i <= n; i ++ )   res = max(res, f[i]);
    
    cout << res;
    return 0;
}

转移路径开一个记忆数组来存储下路径
代码:
 

/*
一、状态表示f[i]。①集合:所有以第i个数结尾的上升子序列。②属性:某一个以i结尾的上升子序列的max
二、状态计算:上升子序列都是以ai结尾,我们以第a(i - 1)个数来进行分类(因为序列是严格递增的),查看前一位即可。
如果第i - 1个数为空,即没有第i - 1个数,那么这个序列长度为1(只有i)。如果第i - 1个数为a1,
那么该长度为:以a1结尾的子序列的长度+1。
如果第i - 1个数为a2,那么该子序列的长度为:以a2结尾的子序列的长度+ 1...
如果第i - 1个数为a(i - 1),那么该子序列的长度为:以a(i - 1)结尾的子序列的长度+ 1。
当然,只有当前一位小于a[i]时,该状态转移才有意义,在转移长度里取一个最大值即可。
最后在已经确定了的序列中取一个最大值即可
*/

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

using namespace std;

const int N = 1010;
int f[N], a[N], g[N];//记忆数组g,记录下来状态转移的路径

int main()
{
    int n;
    cin >> n;
    
    for(int i = 1; i <= n; i ++ )   cin >> a[i];
    
    for(int i = 1; i <= n; i ++ )
    {
        f[i] = 1;//当子序列中只有a[i]这一个数时,序列的长度为1
        for(int j = 1; j <= i; j ++ )
            if(a[j] < a[i])//只有当a[i] > a[j]时,a[i]才能添加到第i - 1个数的后面,才能使子序列严格递增
                if(f[i] < f[j] + 1)//转移的充要条件
                {
                    f[i] = f[j] + 1;
                    //记录下当前状态(i)是由哪一个状态(j)转移而来
                    g[i] = j;
                }
                
    }
    
    int k = 1;
    //找出最大值
    for(int i = 1; i <= n; i ++ )
        if(f[k] < f[i])
            k = i;
    
    cout << f[k] << endl;
    
    
    for(int i = 0, len = f[k]; i < len; i ++ )
    {
        cout << a[k] << " " ;//输出路径
        k = g[k];//寻找k是由哪一个状态转移而来
    }
    
    return 0;
}

路径如右图:

题目:897. 最长公共子序列 - AcWing题库

思路 

闫氏dp分析法:
一、状态表示:f[ i ][ j ](因为是两行字符串进行比较)。①集合:所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列。②属性:子序列长度的最大值
二、状态计算:注意集合的描述:所有在第一个序列的前i个字母中出现,且在第二个序列的前j个字母中出现的子序列。虽然在第一个序列的前i个字母中出现,但子序列不一定包含a[ i ],同理,子序列也不一定包含b[ j ] 。那么我们就以子序列是否包含a[ i ],b[ j ] 来划分区间。
以a[i]、b[j]是否包含在子序列当中作为划分的依据。那么即可分为四种情况:子序列①包含a[i],b[j];②包含a[i],不包含b[j];  ③不包含a[i],包含b[j];④即不包含a[i]也不包含b[j]。
第④种,既然子序列即不包含a[i]也不包含b[i],那么等同于所有在第一个序列的前i - 1个字母中出现
且在第二个序列的前j - 1个字母中出现,则状态为:f[i - 1][j - 1]。
第①种即包含a[i],也包含b[j],那么等同于第④中情况+1。
那么我们来看f[i - 1][j]的含义:在第一个序列的前i - 1个字母中出现,且在第二个序列的前j个字母中出现的子序列
再来看③的含义:子序列里不包含a[i]且子序列里包含b[j]。所以我们可以发现f[i - 1][j]不等同于③,
但是f[i - 1][j]包含③(③的范围更小些)。这么来看,虽然不能等同,但是范围要大于③并且f[i - 1][j]∈f[i][j]
所以情况③的max可由f[i - 1][j]进行求解。假设③情况的最大值为max1,f[i - 1][j]的最大值为max',求要求解的最大值为MAX,那么max1∈max'∈MAX,所以是不影响求解的。 同理②∈f[i][j - 1]。
而由于④是包含于①、②、③的并集,所以④通常情况下不写。记f[i][j - 1]的最大值为max'',f[i - 1][j - 1]+1 的最大值为max'''那么MAX包含于max'∪max''∪max'''
比如(1,2,3)的最大值为3, (1, 2, 4)的最大值为4, (1, 3, 5)的最大值为5,而(1,2,3,4,5)的最大值为5∈(3,4,5)。
分析下来,结果即是在f[i - 1][ j ],f[ i ][ j - 1 ],f[ i - 1 ][ j - 1 ] + 1当中取一个最大值。
注意f[ i - 1 ][ j - 1 ] + 1的情况是在a[ i ] == b[ j ]的情况下特殊处理

代码:

#include<iostream>
#include<cstring>

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]);
            //特殊情况特殊判断,因为当子序列包含a[i],b[j]时即为不包含时的情况+1,前者借助后者
            if(a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
        }
        
    cout << f[n][m] << endl;
    
    return 0;
}

 上述代码属于取巧,即先取一般情况下的max,再与特殊情况取一个max。
核心代码应为:

if(a[i] == b[j]) f[i][j] = f[i - 1][j - 1] + 1;
            else f[i][j] = max(f[i - 1][j], f[i][j - 1]);

题目:902. 最短编辑距离 - AcWing题库

思路 

闫氏dp法
一、状态表示f[i][j]:①集合:将所有a[1 ~ i]变成b[1 ~ j]的操作方式。②属性:操作方式的min
二、状态计算:
删:在删去数组a的第i个字母之前,只有a的1~i-1与b的1~i-1完全匹配,才能在删去数组a的第i个字母之后使a与b相等。f[i][j] = f[i - 1][j] + 1(上一个状态的操作次数的最小值 + 1)
增:增加a[i + 1]使a与b相等,那么在增加a[i + 1]之前a数组的1~i与b数组的1~j-1完全匹配。那么新增的a[i + 1]与b[j]相同,所以在数组a的第i + 1上新增b[j]。f[i][j] = f[i][j - 1] + 1
改:将a[i]变成b[j]之后a与b相等。那么改之前a的1~i-1与b的1~j-1完全匹配。(相当于先把a[i]删去,再将b[j]增添到i上)。改与删、增不同,删与增都已经确定了a与b不匹配,而改不能完全确定。所以当a[i] == b[j]时,无需更改直接继承:f[i][j] = f[i - 1][j - 1]。a[i]!=b[j] 那么继承后加上更改的这一步:f[i][j] = f[i - 1][j - 1] +1

代码:

#include<iostream>
#include<algorithm>

using namespace std;

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

int main()
{
    cin >> n >> a + 1;
    cin >> m >> b + 1;
    
    for(int i = 0; i <= m; i ++ ) f[0][i] = i;//数组a为空时,需要增加数组b的所有字母
    for(int j = 0; j <= n; j ++ ) f[j][0] = j;//数组b为空时,需要删除数组a的所有字母
    
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        {
            //先计算常规的普通情况
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            //a[i] == b[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);
        }

    //将数组a的前n个字母变成b的前m个字母
    cout << f[n][m] << endl;
    return 0;
}

本题需要预处理的原因为 本题的状态转移都是建立在数组a与数组b都不为空的前提下,所以两层for循环的状态转移是不包括数组a与数组b为空的情况,那么需要提前预处理出来为空的情况。

题目:899. 编辑距离 - AcWing题库

思路同上一题。
在C++中,可以通过一位数组来访问二维数组。这是因为二维数组在内存中实际上是按照行优先(row-major order)的方式展开的。 
例如str[N][M]声明了N+1行M+1列的数组,我们可以通过str[i]+j的方式来进行访问,表示第i+1行第j+1列。


代码:

#include<iostream>
#include<algorithm>
#include<string.h>

using namespace std;

const int N = 15, M = 1010;
int n, m;
char str[M][N];
int f[N][N];

int check(char str[], char s[])
{
    //获取子串、母串的长度
    int n1 = strlen(str + 1), m1 = strlen(s + 1);
    for(int i = 0; i <= m1; i ++ ) f[0][i] = i;
    for(int i = 0; i <= n1; i ++ ) f[i][0] = i;
    
    for(int i = 1; i <= n1; i ++ )
        for(int j = 1; j <= m1; j ++ )
        {
            f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
            if(s[j] == str[i]) 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[n1][m1];
}

int main()
{
    cin >> n >> m;
    //输入n行数据
    //从第一行第二列开始输入,避免从第一列输入数据,防止边界问题
    for(int i = 0; i < n; i ++ ) cin >> (str[i] + 1);
    
    while(m -- )
    {
        int x, ans = 0;
        char s[N];
        //从第二列开始输入,防止边界问题
        cin >> (s + 1) >> x;
        //遍历所要比较的每一个子串
        for(int i = 0; i < n; i ++ )
        {
            int t = check(str[i], s);
            if(t <= x) ans ++ ;
        }
        
        cout << ans << endl;
    }
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1942882.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hostspot2.0网络是什么?

Hotspot 2.0是一种无线网络技术标准&#xff0c;它是由Wi-Fi联盟推出的&#xff0c;旨在改善公共Wi-Fi热点的用户体验&#xff0c;简化连接流程&#xff0c;提升安全性&#xff0c;并提供更好的漫游体验。Hotspot 2.0也被称为Passpoint&#xff08;Passpoint Release 2&#xf…

Go基础编程 - 12 -流程控制

流程控制 1. 条件语句1.1. if...else 语句1.2. switch 语句1.3. select 语句1.3.1. select 语句的通信表达式1.3.2. select 的基特性1.3.3. select 的实现原理1.3.4. 经典用法1.3.4.1 超时控制1.3.4.2 多任务并发控制1.3.4.3 监听多通道消息1.3.4.4 default 实现非堵塞读写 2. …

【全网首发】小红书最新引流法:轻松留联系方式卡片,直接上演无举报窗口

大家好&#xff01;我是闷声轻创&#xff01;根据最新消息小红书卡片可能会被禁止掉&#xff0c;这对我们的引流矩阵有真不小的冲击&#xff0c;毕竟小红书作为国内领​先的年轻人的生活分享社区&#xff0c;但上有政策下有对策&#xff0c;我今天发现了一个新的留V的方法&…

七天打造一套量化交易系统:Day2-量化交易策略基本模型及要点

七天打造一套量化交易系统&#xff1a;Day2-量化交易策略基本模型及要点 前期回顾趋势型策略模型原理收益分布重点&#xff1a;什么因素能改进策略&#xff08;截断亏损&#xff0c;让利润奔跑&#xff09;要点总结 均值回复型策略模型原理收益分布重点&#xff1a;避免大额亏损…

去掉roscore的python依赖概述

去掉roscore的python依赖概述 文章目录 去掉roscore的python依赖概述roscore有哪些功能思路关于rosmaster本身及其API的介绍 需要实现的核心API代码实现附录(网图) roscore有哪些功能 启动一个rosmaster节点 调用roslaunch在子进程中&#xff08;popen&#xff09;启动rosmast…

浪潮自研交换机系列常见问题处理

CN61108PC-V-H 不能PING通任何地址&#xff0c;也不能被PING 输入ip traceroute enable既可。注意视图 交换机通过console口远程登录至其他交换机&#xff0c;掉线后console口无法使用 例如有2台交换机A和B&#xff0c;在A交换机上插上console线登录后&#xff0c;在A通过SSH…

【入门教程一】基于DE2-115的My First FPGA 工程

1.1. 概述 这是一个简单的练习&#xff0c; 可以帮助初学者开始了解如何使用Intel Quartus 软件进行 FPGA 开发。 在本章节中&#xff0c;您将学习如何编译 Verilog 代码&#xff0c;进行引脚分配&#xff0c;创建时序约束&#xff0c;然后对 FPGA 进行编程&#xff0c;驱动开…

数据结构:二叉搜索树(简单C++代码实现)

目录 前言 1. 二叉搜索树的概念 2. 二叉搜索树的实现 2.1 二叉树的结构 2.2 二叉树查找 2.3 二叉树的插入和中序遍历 2.4 二叉树的删除 3. 二叉搜索树的应用 3.1 KV模型实现 3.2 应用 4. 二叉搜索树分析 总结 前言 本文将深入探讨二叉搜索树这一重要的数据结构。二…

【Vite】快速入门及其配置

概述 Vite是前端构建工具。vite 相较于webpack,vite采用了不同的运行方式&#xff1a; 开发时&#xff0c;并不对代码打包&#xff0c;而是直接采用ESM的方式来运行项目在项目打包部署时&#xff0c;使用 rollup 对项目进行打包除了速度外&#xff0c;vite使用起来也更加方便…

FPGA-ROM IP核的使用

1.理论 ROM全称&#xff1a;Read-Only Memory&#xff0c;也就是只读型固态半导体存储器&#xff0c;即一旦存储信息&#xff0c;无法再改变&#xff0c;信息也不会因为电源关闭消失。但在FPGA中&#xff0c;实际使用的ROM IP核并不是真正的ROM&#xff0c;其实都是内部的RAM资…

关于企业开展数据资产入表新模式

随着数字化转型持续推进&#xff0c;数据的资产化已成为数字时代不可逆转的趋势。企业数据资产入表已进入倒计时&#xff0c;企业是否科学高效地管理与评估数据&#xff0c;影响着企业是否能够意识到数据应作为资产存在&#xff0c;是否将数据纳入财务报表&#xff0c;并利用数…

【YOLOv5/v7改进系列】引入CoordConv——坐标卷积

一、导言 与标准卷积层相比&#xff0c;CoordConv 的主要区别在于它显式地考虑了位置信息。在标准卷积中&#xff0c;卷积核在输入上滑动时&#xff0c;仅关注局部区域的像素强度&#xff0c;而忽略其绝对位置。CoordConv 通过在输入特征图中添加坐标信息&#xff0c;使得卷积…

探索PyPDF2:Python中的PDF处理大师

探索PyPDF2&#xff1a;Python中的PDF处理大师 1. 背景介绍 在数字化时代&#xff0c;PDF文件因其跨平台的兼容性和内容的稳定性而广受欢迎。然而&#xff0c;处理PDF文件&#xff0c;如合并、分割、提取文本等&#xff0c;往往需要专门的工具。这就是PyPDF2库的用武之地。PyP…

Git报错fatal: detected dubious ownership in repository

报错信息 fatal: detected dubious ownership in repository at 解决办法 一行代码解决 git config --global --add safe.directory "*";如何使用git工具初始胡项目并且和远程仓库建立联系 git init–建立一个本地仓库 git add README.md–将README.md文件加入…

【技术升级】Docker环境下Nacos平滑升级攻略,安全配置一步到位

目前项目当中使用的Nacos版本为2.0.2&#xff0c;该版本可能存在一定的安全风险。软件的安全性是一个持续关注的问题&#xff0c;尤其是对于像Nacos这样的服务发现与配置管理平台&#xff0c;它在微服务架构中扮演着核心角色。随着新版本的发布&#xff0c;开发团队会修复已知的…

代码签名证书的作用

代码签名证书&#xff08;Code Signing Certificate&#xff09;主要用于验证软件的完整性和开发者身份&#xff0c;确保用户在下载或安装软件时能够确认该软件未被篡改&#xff0c;并且确实来自于其所声称的发布者。以下是代码签名证书的主要作用&#xff1a; 验证软件来源&am…

Vue Promise 必须在外层,放到其它比如ElMessageBox,将不会返回任何值

当点击switch按钮之前&#xff0c;如果当更新后再刷新的效果不好&#xff0c;需要判断行为&#xff0c;然后再决定是否打开按钮。 正确如下&#xff1a; return new Promise((resolve,reject) > {ElMessageBox.confirm(Hold?, Warning, {confirmButtonText: Yes,cancelButt…

优秀的Linux Shell终端Starship Shell的安装和配置

文章目录 简介安装startship1.安装 starship 二进制文件:2.将初始化脚本添加到您的 shell 的配置文件3、配置4、日志安装字体nerd-fonts编写脚本安装字体Nerd字体全量安装文档简介 Starship是一款轻量、迅速、可无限定制的高颜值终端! Starship Shell是一个用Rust编写的开源…

虚拟机迁移报错:虚拟机版本与主机“x.x.x.x”的版本不兼容

1.虚拟机在VCenter上从一个ESXi迁移到另一个ESXi上时报错&#xff1a;虚拟机版本与主机“x.x.x.x”的版本不兼容。 2.例如从10.0.128.13的ESXi上迁移到10.0.128.11的ESXi上。点击10.0.128.10上的任意一台虚拟机&#xff0c;查看虚拟机版本。 3.确认要迁移的虚拟机磁盘所在位…

怎么理解FPGA的查找表与CPLD的乘积项

怎么理解 fpga的查找表 与cpld的乘积项 FPGA&#xff08;现场可编程门阵列&#xff09;和CPLD&#xff08;复杂可编程逻辑器件&#xff09;是两种常见的数字逻辑器件&#xff0c;它们在内部架构和工作原理上有着一些显著的区别。理解FPGA的查找表&#xff08;LUT&#xff0c;L…