Acwing 线性DP

news2024/10/5 19:43:51

状态转移方程呈现出一种线性的递推形式的DP,我们将其称为线性DP。

Acwing 898.数字三角形

在这里插入图片描述
实现思路:

  • 对这个三角形的数字进行编号,状态表示依然可以用二维表示,即f(i,j),i表示横坐标(横线),j表示纵坐标(斜线)
    在这里插入图片描述
    在这里插入图片描述
  • f(i,j)表示到点(i,j)的路径最大数字之和。对集合进行划分,到达某点(i,j)只可能经过左上方的点(i-1,j-1)或右上方的点(i-1,j)。用a[i][j]表示当前点的数值;
  • 故可得状态转移方程:f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j])

具体实现代码(详解版):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 510, INF = 1e9;  
int n; 
int a[N][N];  // 存储三角形中的数字
int f[N][N];  // 动态规划数组,存储从顶点到达每个位置的最大路径和

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

    // 初始化 DP 数组,将所有 f[i][j] 初始化为一个极小值,表示不可到达
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= i + 1; j++) {
            f[i][j] = -INF;
        }
    }

    // 设置起始点,顶部元素的最大路径和就是它自身
    f[1][1] = a[1][1];

    // 状态转移方程:f[i][j] 表示到达第 i 行第 j 列的最大路径和
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            // 到达 f[i][j] 位置有两种可能:
            // 1. 来自 f[i-1][j-1],即从左上角过来
            // 2. 来自 f[i-1][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++) {
        res = max(res, f[n][i]);  // 找出最后一行的最大路径和
    }

    cout << res << endl;  
    return 0;
}

这道题还可以从下往上递推,考虑f[i][j]来自左下方和来自右下方两种情况,这样就不需要处理边界问题,而且最后的结果一定集中在f[1][1]中。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 510, INF = 1e9;  // 定义常量 N 为最大行数,INF 为极大值
int n;  // 表示三角形的行数
int f[N][N], a[N][N];  // f 用于存储从底部到达每个位置的最大路径和,a 存储三角形中的数字

int main() {
    int n;
    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];
    
    // 自底向上递推,计算每一行的最大路径和
    for (int i = n - 1; i >= 1; i--) {
        for (int j = 1; j <= i; j++) {
            // f[i][j] 表示在 (i, j) 位置的最大路径和
            f[i][j] = max(f[i + 1][j], f[i + 1][j + 1]) + a[i][j];
        }
    }

    // 输出顶点处的最大路径和
    cout << f[1][1] << endl;
    return 0;
}

Acwing 895.最长上升子序列

在这里插入图片描述
实现思路:
在这里插入图片描述

  • 一维数组f[i]表示以第i个数为结尾的最长递增子序列的长度;
  • 状态划分:选定i为结尾的递增子序列,则再从[0,i-1]中筛选出倒数第二个位置的数,使递增子序列的长度最大。注意这个倒数第二个位置的数必须满足a[j]<a[i],这样才能保证递增序列
  • 状态转移方程为f[i]=max(f[i],f[j]+1);

具体实现代码(详解版):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010; 

int n; 
int a[N], f[N];  // a 存储输入的数组, f 存储以每个元素结尾的最长上升子序列的长度

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的子序列
        for(int j = 1 ; j < i ; j ++){
            // 如果前面的元素 a[j] 比当前元素 a[i] 小,则可以考虑将 a[i] 接在 a[j] 
            //之后形成一个更长的子序列
            if(a[j] < a[i]) 
                f[i] = max(f[i], f[j] + 1);  // 更新 f[i],选择使 f[i] 最大的方案
        }
    }

    // 找到 f 数组中的最大值,即最长上升子序列的长度
    int res = 0;
    for(int i = 1 ; i <= n ; i ++) 
        res = max(res, f[i]);
    
    cout << res << endl;  
    
    return 0;
}

那有没有办法进行优化呢?最长上升子序列(LIS)问题的时间复杂度为 O ( n 2 ) , O(n^2), O(n2),我们可以通过贪心算法 + 二分查找来将时间复杂度优化为 O ( n l o g n ) O(nlogn) O(nlogn).

Acwing 896. 最长上升子序列 II

在这里插入图片描述
实现思路:

  • 首先在上述解法的基础上,假如存在一个序列3 1 2 5,以3结尾的上升子序列长度为1,以1为结尾的上升子序列长度也为1,这是两个长度一样的上升子序列(伏笔:结尾元素1<3)。在继续向后遍历查找时,看3这个序列,当出现一个比3大的数时,以3结尾的上升子序列就会更新,比如遍历到5了,那么上升序列变为3 5;同时注意到这个5一定会加入到以1结尾的上升序列中(因为1<3,那么1<5的),那么含有1的上升序列长度一定是>=2的,因为中间可能存在<3但>1的数(比如这里就有2,序列长度就更新为3)。可以看出存在3的这个序列就不需要枚举了,因为存在1的序列往后遍历的长度是一定大于你这个存在3的序列的(前提是以1结尾和以3结尾的上升序列长度相等),那我找最长的时候怎么都轮不到包含3的序列头上,那我一开始在1和3结尾的序列之后直接舍弃枚举包含3的序列了(去掉冗余)。
  • 在以上的分析得到:当存在两个上升序列长度相同时,结尾数更大的序列可以舍去不再枚举,所以每次就干脆选出相同长度结尾元素最小的序列继续操作
  • 那么状态表示更改为:f[i]表示长度为i+1(因为下标从0开始)的最长上升子序列,末尾最小的数字。(所有长度为i+1的最长上升子序列所有结尾中,结尾最小的数) 即长度为i的子序列末尾最小元素是什么。
  • 状态计算:序列长度+1(cnt++),当前末尾最小元素变为a[i]。 **若a[i]小于等于f[cnt-1],**说明不会更新当前的长度,但之前末尾的最小元素要发生变化,找到第一个 大于或等于(不能直接写大于,要保证单增) a[i]的数的位置mid,将这个数a[i]放在mid的位置(其实就是找到a[i]适合存在的位置,不改变序列长度)。

具体实现代码(版本一):

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 100010;  // 定义最大数组大小
int n, cnt;
int a[N], f[N];

int main() {
    // 输入数组长度
    cin >> n;

    // 输入数组元素
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    // 初始化 cnt
    cnt = 0;

    // 处理第一个元素
    f[cnt++] = a[0];

    for (int i = 1; i < n; i++) { // 注意这里应为 i < n
        if (a[i] > f[cnt - 1]) {
            f[cnt++] = a[i]; // 如果 a[i] 大于当前上升序列末尾的数,则末尾加入
        } else {
            // 使用二分查找
            int l = 0, r = cnt - 1;
            while (l < r) {
                int mid = (l + r) / 2;
                if (f[mid] >= a[i]) r = mid; // 找到第一个 >= a[i] 的位置
                else l = mid + 1;
            }
            f[r] = a[i]; // 替换找到的位置
        }
    }

    cout << cnt << endl; // 输出最长上升子序列的长度

    return 0;
}

版本二(使用lower_bound函数,找到第一个 >= a[i] 的位置)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 100010;  // 定义最大数组大小
int n;
int a[N];

int main() {
    // 输入数组长度
    cin >> n;

    // 输入数组元素
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    // 用于维护当前的上升子序列
    vector<int> d;

    // 遍历数组的每个元素
    for (int i = 0; i < n; i++) {
        // 使用二分查找找到第一个 >= a[i] 的位置
        auto it = lower_bound(d.begin(), d.end(), a[i]);

        if (it == d.end()) {
            // 如果没有找到比 a[i] 大的元素,则将 a[i] 添加到序列末尾
            d.push_back(a[i]);
        } else {
            // 否则,用 a[i] 替换掉找到的这个位置的元素
            *it = a[i];
        }
    }

    // 最终 d 的大小就是最长上升子序列的长度
    cout << d.size() << endl;

    return 0;
}

Acwing 897. 最长公共子序列

在这里插入图片描述
实现思路:
在这里插入图片描述

  • f(i,j)表示第一个序列的前i个字母中出现并且在第二序列前j个字母中出现的最长的公共子序列长度
  • 状态可划分为4种情况:a[i]表示为第一个序列中第i个字符,b[j]表示第二个子序列中第j个字符
    • 00:表示最长公共子序列中一定不包含字符a[i]和b[j],用f[i-1][j-1]表示
    • 01:表示最长公共子序列中一定不包含字符a[i],一定包含b[j]。不能用f[i-1][j]表示(不是等价的),因为f[i-1][j]表示的是该公共子序列一定不包含a[i],但b[j]不一定,可能包含也可能不包含f[i-1][j]是包含01这种情况的。但是由于求的是最大子序列的长度(而不是具体元素),所以求解的时候可以用f[i-2][j]来求解
    • 10:表示最长公共子序列中一定包含字符a[i],一定不包含b[j]。用f[i][j-1]求解,但含义不等价,同上。
    • 11:表示最长公共子序列中一定包含字符a[i]和b[j],用f[i-1][j-1]+1表示,但注意需要满足a[i] = b[j]才行,因为公共子序列,既然包含a[i]、b[i],那么两者必然相等才行
  • 00的情况实质上已经被包含在01、10两种情况之中,所以可以省略,故只需求下面三种状态

具体实现代码(详解版):

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

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

int main() {
    
    cin >> n >> m;
    // 输入字符串,保留 a[0] 和 b[0] 为空字符
    cin >> (a + 1) >> (b + 1);

    // 动态规划计算最长公共子序列
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            // 如果字符相同,则长度加 1
            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]);
            }
        }
    }

    // 输出最长公共子序列的长度
    cout << f[n][m] << endl;

    return 0;
}

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

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

相关文章

pygame--超级马里奥(万字详细版)

超级马里奥点我下载https://github.com/marblexu/PythonSuperMario 1.游戏介绍 小时候的经典游戏&#xff0c;代码参考了github上的项目Mario-Level-1&#xff0c;使用pygame来实现&#xff0c;从中学习到了横版过关游戏实现中的一些处理方法。原项目实现了超级玛丽的第一个小…

Windows应急响应-Auto病毒

文章目录 应急背景分析样本开启监控感染病毒查看监控分析病毒行为1.autorun.inf分析2.异常连接3.进程排查4.启动项排查 查杀1.先删掉autorun.inf文件2.使用xuetr杀掉进程3.启动项删除重启排查入侵排查正常流程 应急背景 运维人员准备通过windows共享文档方式为公司员工下发软件…

【Java】Java面试题笔试

[赠送]面试视频指导简历面试题 Java面试笔试题题库华为 java笔试面试题2014.files 就业相关java 面试题 面试题库 视频笔记 java笔试题大集合及答案 java面试书籍源码 java算法大全源码包8张图解 java.docx25个经典的Spring面试问答.docx 25个经典的Spring面试问答.docx 100家大…

知识图谱入门——10:使用 spaCy 进行命名实体识别(NER)的进阶应用:基于词袋的实体识别与知识抽取

在构建知识图谱的过程中&#xff0c;如何准确地识别和提取实体是关键。spaCy 提供了强大的命名实体识别&#xff08;NER&#xff09;功能&#xff0c;我们可以结合自定义规则和工具来实现更精准的实体抽取。本文将详细探讨如何在 spaCy 中实现自定义实体抽取&#xff0c;包括使…

OpenAI 推出 Canvas 工具,助力用户与 ChatGPT 协作写作和编程

OpenAI 近日推出了一款名为 Canvas 的新工具&#xff0c;旨在帮助用户更高效地与 ChatGPT 协作进行写作与编程。 Canvas 允许用户在一个独立窗口中与 ChatGPT 实时协作修改内容。无论是改进文本、调整语言风格、审查代码&#xff0c;还是在不同编程语言间转换&#xff0c;Canv…

Js逆向分析+Python爬虫结合

JS逆向分析Python爬虫结合 特别声明&#x1f4e2;&#xff1a;本教程只用于教学&#xff0c;大家在使用爬虫过程中需要遵守相关法律法规&#xff0c;否则后果自负&#xff01;&#xff01;&#xff01; 完整代码地址Github&#xff1a;https://github.com/ziyifast/ziyifast-co…

自闭症干预寄宿学校:为孩子搭建沟通与社交的桥梁

在探索自闭症儿童教育的广阔天地里&#xff0c;一所优秀的寄宿学校不仅是知识的殿堂&#xff0c;更是孩子们学习沟通与社交技能的桥梁。位于广州的星贝育园自闭症儿童寄宿制学校&#xff0c;正是这样一所专注于为自闭症儿童提供全面、个性化教育服务的机构&#xff0c;它以其独…

Linux-du命令使用方法

Linux-du&#xff08;disk useage&#xff09;命令 du 命令用于查看文件和目录占用的磁盘空间。 du [选项] [文件或目录]-h (human-readable)&#xff1a; 将输出格式转为人类可读的形式&#xff0c;使用 KB、MB 等单位。 du -h /path/to/directory1.5M /path/to/directory…

Pikachu-SSRF(curl / file_get_content)

SSRF SSRF是Server-side Request Forge的缩写&#xff0c;中文翻译为服务端请求伪造。产生的原因是由于服务端提供了从其他服务器应用获取数据的功能且没有对地址和协议等做过滤和限制。常见的一个场景就是&#xff0c;通过用户输入的URL来获取图片。这个功能如果被恶意使用&am…

Linux 之 安装软件、GCC编译器、Linux 操作系统基础

安装软件、GCC编译器、Linux 操作系统基础 学习任务&#xff1a; 安装 Vmware虚拟机、掌握Ubuntu 系统的使用认识 Ubuntu 操作系统的终端和 Shell掌握软件安装、文件系统、掌握磁盘管理与解压缩掌握 VIM 编辑器、Makefile 基本语法熟悉 Linux 常见指令操作 安装好开发软件&…

力扣189.轮转数组

给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4…

数据库(MySQL):使用命令从零开始在Navicat创建一个数据库及其数据表(一).创建基础表

一. 使用工具和命令 1.1 使用的工具 Navicat Premium 17 &#xff1a;“Navicat”是一套可创建多个连接的数据库管理工具。 MySQL版本8.0.39 。 1.2 使用的命令 Navicat中使用的命令 命令命令解释SHOW DATABASES&#xff1b;展示所有的数据库CREATE DATABASE 数据库名称; 创…

10以内数的分解

// 10以内数的分解.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 //#include <iostream> using namespace std; int main(int argc, char* argv[]){for (int i 2; i < 10; i){for (int j 1; j < i; j){printf("%d%d%d ",j…

CSS样式基础样式选择器(案例+代码实现+效果图)

目录 1.css样式的规则 2.引入css样式的方式 1)行内式 2)内嵌式 3)外链式 1-link导入 2-import导入 4)总 3.css基础选择器 1)标签选择器 案例&#xff1a;使用标签选择器编写一个圆 1.代码 2.效果 2)类选择器 案例&#xff1a;使用类选择器为div添加背景色 1.代码 2.效果 3)id…

深度探索Kali Linux的精髓与实践应用

Kali Linux简介 Kali Linux作为全球网络安全领域的首选操作系统之一&#xff0c;其强大的功能性及广泛的适用范围令人瞩目。除了上述基础介绍外&#xff0c;让我们深入探究Kali Linux的几个关键特性及其在实际操作中的具体应用案例。 Kali工具集成&#xff1a;全面的安全工具…

加湿器模块(5V STM32)

目录 一、介绍 二、模块原理 1.原理图 2.引脚描述 3.尺寸介绍 三、程序设计 main.c文件 jsq.h文件 jsq.c文件 四、实验效果 五、资料获取 项目分享 一、介绍 加湿器模块通常是指在智能家居设备中负责增加室内空气湿度的部分&#xff0c;这种模块设计旨在改善干燥环…

osg 矩阵相关

下面结果是一样的 osg::Matrix mtrixx;mtrixx.makeRotate(90 / 180.f * osg::PI, osg::Vec3(1, 0, 0));osg::Matrix mtrixx12 osg::Matrix::rotate(90 / 180.f * osg::PI, 1, 0, 0); 备注&#xff1a; rotate或makerotate第一个参数是弧度&#xff0c;可以用 弧度值osg::in…

Pre-trained Natural Language Understanding for Task-Oriented Dialogue

前言 众所周知,预训练BERT语言模型在许多NLP任务重大放异彩,用来文本内容和语义的表征学习很有效果,而且大大降低了下游任务的训练时间。但是由于普通文本和任务型对话之间的语言模式的潜在差异,使得现在的预训练语言模型在实际使用中作用不大。 至于对话领域的预训练语言…

MS-900认证:Microsoft 365 Certified: Fundamentals

一、什么是MS-900认证&#xff1f; MS900认证&#xff0c;全称是 Microsoft 365 Fundamentals&#xff0c;是微软提供的一项认证考试&#xff0c;它旨在验证考生对 Microsoft 365 服务和功能的理解&#xff0c;包括云服务概念、核心服务、安全性、合规性、隐私以及支持和定价信…

【CTF Web】Pikachu CSRF(get) Writeup(CSRF+GET请求+社会工程学)

CSRF(跨站请求伪造)概述 Cross-site request forgery 简称为“CSRF”&#xff0c;在CSRF的攻击场景中攻击者会伪造一个请求&#xff08;这个请求一般是一个链接&#xff09;&#xff0c;然后欺骗目标用户进行点击&#xff0c;用户一旦点击了这个请求&#xff0c;整个攻击就完成…