Hz的DP总结

news2025/3/6 9:24:10

前言:

鉴于本人是一个DP低手,以后每写一道DP都会在本篇博客下进行更新,包括解题思路,方法,尽量做到分类明确,其中的题目来自包括但并不限于牛客,洛谷,CodeForces,AtCoder等平台,希望有朝一日也能独立写出2000分的DP。

正片:

线性DP:

Educational Codeforces Round 174 (Rated for Div. 2) C. Beautiful Sequence

在这里插入图片描述
这道题真是赛时不知道怎么推,赛后一看题解就觉得自己是个XX。

  • 知识点:思维,递推

简单来讲就是要求一下形如 1 2 2 2 … 2 3 这样的子序列的个数。

考虑线性递推:f[i][1, 2, 3] 表示以第i个数字结尾的,且a[i] = 1, 2, 3 的满足条件的序列数;
这里对状态方程的定义有点模糊,因为这里严格来讲不太像DP,更像是一种递推。

看一下转移,首先借助前缀和的思想,f[i] = f[i - 1],三个状态的数量都等于上一步的数量,再加上这一步的贡献。

  • 对于a[i] == 1;
    f[i][1] += 1;
  • 对于a[i] == 2;
    f[i][2] += f[i - 1][2];
    // 可以看作这个2可以加在之前任何一个以2结尾的序列的末尾,所以就是加上之前所有的。
    f[i][2] += f[i - 1][1];
    // 也可以是直接接在之前任何一个1的末尾,所以加上1的数量。
  • 对于a[i] == 3;
    f[i][3] += f[i][2];
    就是答案。
#include<bits/stdc++.h>

using namespace std;
using i64 = long long;
using u64 = unsigned long long;

#define int long long
#define debug(x) cerr << #x" = " << x << '\n';

typedef pair<int, int> PII;

const i64 N = 2e5 + 10, INF = 1e18 + 10;

int mod = 998244353;

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    vector<int> f(4, 0);
    for (int i = 1; i <= n; i++) {
        if (a[i] == 1) {
            (f[1] += 1) %= mod;
        } else if (a[i] == 2) {
            (f[2] += f[2]) %= mod;
            (f[2] += f[1]) %= mod;
        } else if (a[i] == 3) {
            (f[3] += f[2]) %= mod;
        }
    }
    cout << f[3] % mod << endl;
}

signed main()
{
    cin.tie(0) -> ios::sync_with_stdio(false);
    int T = 1;
    cin >> T;
    while (T --) solve();
    return 0;
}

这里其实不难发现每一次f 都是由 i - 1转移过来,所以可以直接省去一维。

背包DP:

牛客周赛 Round 81 E.建筑入门

在这里插入图片描述

  • 知识点:完全背包,完全背包的路径转移以及回溯

由题意可以推导出,下层麻将的数字一定大于上层数字,所以我们可以假设一个最基础的麻将塔,也就是:
1
2 2
3 3 3

形如这样的,然后再从下往上一层一层加数字,且得保证下层一定得大于上层,所以可以想象成,如果倒数第二层加数字的话,倒数第一层也得加数字,那就可以翻译成 选择一个从第i层向下的后缀层数,每一层每一个块均+1,然后选择若干这样的后缀和,最后使得总值正好等于k。

这也就是转化成了一个经典的完全背包问题。
f[i][j] 表示 前i个数字里面选择之后数字和恰好为j能否做到,记录的是一个bool类型的值

但是还有个问题:如何记录路径?
学过01背包的小伙伴肯定都知道01背包记录路径的方法,只需要记录选择了哪件物品即可,但是介于完全背包每个物品可以选择很多件,所以不能单单记录选择了哪件物品,这就需要记录我们的背包体积是什么时候发生变化的。

vector<PII> path(k + 1);  // 记录一下当前体积为 i 的上一步体积是多少

一些回溯包括其他的细节大家参考代码理解吧

#include<bits/stdc++.h>

using namespace std;
using i64 = long long;
using u64 = unsigned long long;

#define int long long
#define debug(x) cerr << #x" = " << x << '\n';

typedef pair<int, int> PII;

const i64 N = 2e5 + 10, INF = 1e18 + 10;

void solve()
{
    int n, k;
    cin >> n >> k;
    k = k - n * (n + 1) * (2 * n + 1) / 6;  // 1^1 + 2^2 + 3^3 + ... + n^n
    if (k < 0) {
        cout << -1 << endl;
        return;
    }
    vector<int> a(n + 2);
    for (int i = n; i > 0; i--) {
        a[i] = a[i + 1] + i;
    }
    vector<vector<int>> f(n + 1, vector<int>(k + 1, 0));  // 前i个数字里面选择之后数字和恰好为j能否做到
    vector<PII> path(k + 1);  // 记录一下当前体积为 i 的上一步体积是多少
    f[0][0] = 1;
    for (int i = 1; i <= n; i++) {  // (以倒数第n行为开始的后缀)(倒数第n - 1行开始的)。。
        for (int j = 0; j <= k; j++) {
            f[i][j] = f[i - 1][j];
            if (j - a[i] >= 0 && f[i][j - a[i]]) {
                f[i][j] = 1;
                path[j] = {j - a[i], i};
                // 体积为 j 的上一步是 j - a[i];
                // 把第i号物品拿过来之后进行的转移
            }
        }
    }
    if (!f[n][k]) {
        cout << -1 << endl;
        return;
    }
    vector<int> nums;
    int curr = k;
    while(curr > 0) {
        auto [pre, x] = path[curr];
        nums.emplace_back(x);
        curr = pre;
    }
    vector<int> ans(n + 1, 0);
    for (auto &x : nums) {
        ans[x]++;
    }
    for (int i = 1; i <= n; i++) {
        ans[i] += ans[i - 1];
    }
    for (int i = 1; i <= n; i++) {
        ans[i] += i;
        cout << ans[i] << " \n"[i == n];
    }
}

signed main()
{
    cin.tie(nullptr) -> ios::sync_with_stdio(false);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

和数据结构结合的DP:

单调栈:Codeforces Round 622 (Div. 2) C2. Skyscrapers (hard version)

在这里插入图片描述
简单来讲就是最后需要呈现出一个单峰数组,使得总高度最高。

最开始想到暴力枚举每一个元素都充当最高的“单峰”,但是这里的 n 过大,这样枚举肯定会TLE。

那就考虑能不能单调线性的考虑每个元素作为最高点的时候的解是多少呢?

这里就需要借助我们的 单调栈,维护一个单调递增的序列:

  • 这里仅以正序遍历为例:f[i]表示的是以i为单峰时1 — i 所有数组能产生的最大贡献,根据单调栈的性质,stk.top()就是上一个最近的小于 a[i] 的元素的下标,所以加上这中间所有的楼产生的贡献(由于单调栈的性质,这段中所有大于a[i] 的元素一定会被弹出,同时减去他们之前产生的贡献)。
#include<bits/stdc++.h>

using namespace std;

#define int long long

signed main() {
    int n, sum = 0;
    cin >> n;
    vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    stack<int> stk;
    vector<int> f(n + 1, 0);
    sum = 0;
    for (int i = 1; i <= n; i++) {
        while(stk.size() && a[stk.top()] >= a[i]) {
        //先弹出所有大于a[i]的楼的下标,因为保证要单增
            int j = stk.top();
            stk.pop();
            sum -= (j - (stk.empty() ? 0 : stk.top())) * a[j];
            // 减去这些楼之前所产生的贡献
        }
        sum += (i - (stk.empty() ? 0 : stk.top())) * a[i]; //加上目前这栋楼所产生的贡献
        stk.push(i);
        f[i] += sum;
    }
    sum = 0;
    while(!stk.empty()) stk.pop(); 
    for (int i = n; i >= 1; i--) {
        while(stk.size() && a[stk.top()] >= a[i]) {
            int j = stk.top();
            stk.pop();
            sum -= ((stk.empty() ? n + 1 : stk.top()) - j) * a[j];
        }
        sum += ((stk.empty() ? n + 1 : stk.top()) - i) * a[i];
        stk.push(i);
        f[i] += sum - a[i];
    }

    auto p = max_element(f.begin() + 1, f.end()) - f.begin();
    //cout << p << endl;
    for (int i = p - 1; i >= 1; i--) {
        a[i] = min(a[i], a[i + 1]);
    }
    for (int i = p + 1; i <= n; i++) {
        a[i] = min(a[i], a[i - 1]);
    }
    for (int i = 1; i <= n; i++) {
        cout << a[i] << " \n"[i == n];
    }

    return 0;
}

建议先熟练掌握单调栈再来理解这题。

概率DP:

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

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

相关文章

【三极管8050和8550贴片封装区分脚位】

这里写自定义目录标题 三极管8050和8550贴片封装区分脚位三极管8050三极管8550 三极管8050和8550贴片封装区分脚位 三极管8050 增加了 检查列表 功能。 [ NPN型三极管&#xff08;SS8050&#xff09; ]: SS8050的使用及引脚判断方法 三极管8550

C# Unity 唐老狮 No.6 模拟面试题

本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…

二、Visual Studio2022配置OpenGL环境

文章目录 一、OpenGL库的下载二、OpenGL环境配置三、测试代码演示 一、OpenGL库的下载 OpenGL配置的库是GLFWGLAD &#xff0c;GLFW 主要用于创建 OpenGL 窗口和管理输入&#xff1b;GLAD 主要用于加载 OpenGL 函数 GLFW下载地址 下载Windows的32bit版本即可。 下载完成解压如…

YOLOv8改进------------SPFF-LSKA

YOLOv8改进------------SPFF-LSKA 1、LSAK.py代码2、添加YAML文件yolov8_SPPF_LSKA.yaml3、添加SPPF_LSKA代码4、ultralytics/nn/modules/__init__.py注册模块5、ultralytics/nn/tasks.py注册模块6、导入yaml文件训练 1、LSAK.py代码 论文 代码 LSKA.py添加到ultralytics/nn/…

240 Vocabulary Words Kids Need to Know

《240 Vocabulary Words Kids Need to Know》是美国学乐出版社&#xff08;Scholastic&#xff09;推出的词汇学习系列练习册&#xff0c;专为美国小学阶段&#xff08;G1-G6&#xff09;设计&#xff0c;基于CCSS&#xff08;美国共同核心州立标准&#xff09;编写&#xff0c…

AI-Deepseek + PPT

01--Deepseek提问 首先去Deepseek问一个问题&#xff1a; Deepseek的回答&#xff1a; 在汽车CAN总线通信中&#xff0c;DBC文件里的信号处理&#xff08;如初始值、系数、偏移&#xff09;主要是为了 将原始二进制数据转换为实际物理值&#xff0c;确保不同电子控制单元&…

【五.LangChain技术与应用】【8.LangChain提示词模板基础:从入门到精通】

早上八点,你端着咖啡打开IDE,老板刚甩来需求:“做个能自动生成产品描述的AI工具”。你自信满满地打开ChatGPT的API文档,结果半小时后对着满屏的"输出结果不稳定"、"格式总出错"抓耳挠腮——这时候你真需要好好认识下LangChain里的提示词模板了。 一、…

LeetCode 718.最长重复子数组(动态规划,Python)

给两个整数数组 nums1 和 nums2 &#xff0c;返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,3,2,1], nums2 [3,2,1,4,7] 输出&#xff1a;3 解释&#xff1a;长度最长的公共子数组是 [3,2,1] 。 示例 2&#xff1a; 输…

python-leetcode-零钱兑换 II

518. 零钱兑换 II - 力扣&#xff08;LeetCode&#xff09; 这个问题是 完全背包问题 的一个变体&#xff0c;可以使用 动态规划 来解决。我们定义 dp[i] 为凑成金额 i 的硬币组合数。 思路&#xff1a; 定义 DP 数组 设 dp[i] 表示凑成金额 i 的组合数&#xff0c;初始化 dp[…

Sass 模块化革命:深入解析 @use 语法,打造高效 CSS 架构

文章目录 前言use 用法1. 模块化与命名空间2. use 中 as 语法的使用3. as * 语法的使用4. 私有成员的访问5. use 中with默认值6. use 导入问题总结下一篇预告&#xff1a; 前言 在上一篇中&#xff0c;我们深入探讨了 Sass 中 import 语法的局限性&#xff0c;正是因为这些问题…

利用Postman和Apipost进行API测试的实践与优化-动态参数

在实际的开发和测试工作中&#xff0c;完成一个API后对其进行简单的测试是一项至关重要的任务。在测试过程中&#xff0c;确保API返回的数据符合预期&#xff0c;不仅可以提高开发效率&#xff0c;还能帮助我们快速发现可能存在的问题。对于简单的API测试&#xff0c;诸如验证响…

【前端基础】Day 9 PC端品优购项目

目录 1. 品优购项目规划 1.1 网站制作流程 1.2 品优购项目整体介绍 1.3 学习目的 1.4 开发工具以及技术栈 1.5 项目搭建工作 1.6 网站favicon图标 1.7 网站TDK三大标签SEO优化 2. 品优购首页制作 2.1 常见模块类命名 2.2 快捷导航shortcut制作 2.3 header制作 2.4…

FFMPEG利用H264+AAC合成TS文件

本次的DEMO是利用FFMPEG框架把H264文件和AAC文件合并成一个TS文件。这个DEMO很重要&#xff0c;因为在后面的推流项目中用到了这方面的技术。所以&#xff0c;大家最好把这个项目好好了解。 下面这个是流程图 从这个图我们能看出来&#xff0c;在main函数中我们主要做了这几步&…

Linux搭建个人大模型RAG-(ollama+deepseek+anythingLLM)

本文是远程安装ollama deepseek&#xff0c;本地笔记本电脑安装anythingLLM&#xff0c;并上传本地文件作为知识库。 1.安装ollama 安装可以非常简单&#xff0c;一行命令完事。&#xff08;有没有GPU&#xff0c;都没有关系&#xff0c;自动下载合适的版本&#xff09; cd 到…

Docker 学习(二)——基于Registry、Harbor搭建私有仓库

Docker仓库是集中存储和管理Docker镜像的平台&#xff0c;支持镜像的上传、下载、版本管理等功能。 一、Docker仓库分类 1.公有仓库 Docker Hub&#xff1a;官方默认公共仓库&#xff0c;提供超过10万镜像&#xff0c;支持用户上传和管理镜像。 第三方平台&#xff1a;如阿里…

韩国互联网巨头 NAVER 如何借助 StarRocks 实现实时数据洞察

作者&#xff1a; Youngjin Kim Team Leader, NAVER Moweon Lee Data Engineer, NAVER 导读&#xff1a;开源无国界&#xff0c;在“StarRocks 全球用户精选案例”专栏中&#xff0c;我们将介绍韩国互联网巨头 NAVER 的 StarRocks 实践案例。 NAVER 成立于 1999 年&#xff0…

C语言学习笔记-初阶(30)深入理解指针2

1. 数组名的理解 在上一个章节我们在使用指针访问数组的内容时&#xff0c;有这样的代码&#xff1a; int arr[10] {1,2,3,4,5,6,7,8,9,10}; int *p &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数组第⼀个元素的地址&#xff0c;但是其实数组名本来就是地址&…

【Wireshark 02】抓包过滤方法

一、官方教程 Wireshark 官网文档 &#xff1a; Wireshark User’s Guide 二、显示过滤器 2.1、 “数据包列表”窗格的弹出过滤菜单 例如&#xff0c;源ip地址作为过滤选项&#xff0c;右击源ip->prepare as filter-> 选中 点击选中完&#xff0c;显示过滤器&#…

MySQL基础四(JDBC)

JDBC(重点) 数据库驱动 程序会通过数据库驱动&#xff0c;和数据库打交道。 sun公司为了简化开发人员对数据库的统一操作&#xff0c;提供了一个Java操作数据库的规范。这个规范由具体的厂商去完成。对应开发人员来说&#xff0c;只需要掌握JDBC接口。 熟悉java.sql与javax.s…

基于CURL命令封装的JAVA通用HTTP工具

文章目录 一、简要概述二、封装过程1. 引入依赖2. 定义脚本执行类 三、单元测试四、其他资源 一、简要概述 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具&#xff0c;可以说是一款很强大的http命令行工具。它支持文件的上传和下载&#xff0c;是综合传输工具&…