Acwing数学与简单DP(二)

摘花生

原题链接:https://www.acwing.com/problem/content/1017/

最后一步,有两种可能:

  • 从上面走
  • 从下面走

也就是max(dp[i-1][j],dp[i][j-1]),再加上最后一个位置的值。

#include"bits/stdc++.h"using namespace std;int dp[110][110];
int T, R, C;int main() {cin >> T;while (T--) {cin >> R >> C;for (int i = 1; i <= R; i++) {for (int j = 1; j <= C; j++) {cin >> dp[i][j];}}for (int i = 1; i <= R; i++) {for (int j = 1; j <= C; j++) {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]) + dp[i][j];}}cout << dp[R][C] << endl;}return 0;
}

最长上升子序列

原题链接:https://www.acwing.com/problem/content/897/

最后一步,有两种可能:

  • 最后一个元素可以与之前的元素构成上升子序列,长度为已知长度+1
  • 最后一个元素没有可组合的子序列,长度为1

最长上升子序列的结束位置未必是最后一个元素,因此需要遍历长度数组,选取最大值。
需要存储:

  • 指向序列某个元素时,截至该元素的最长子序列长度

这可以通过创建一个与原序列等长的dp数组实现。
该数组中,与原序列,下标相同的元素,的值,就是原序列截止到该元素处,的最长上升子序列长度。
需要二层循环,平方复杂度:

  • 外层循环,更新dp数组的第i个元素。
  • 更新到第i个元素时,需要查找前i-1个元素。如果前i-1个元素中,某个元素的值,小于当前要更新的元素的值,那么就可以把当前元素接在这个元素后面。dp[i]的值就是dp[该元素的下标]+1。前i-1个元素可能存在多个小于当前元素的元素,取最大的构成最长子序列。

最后遍历长度数组,求最大值。

#include"bits/stdc++.h"using namespace std;int a[1010], f[1010];
int n;int main() {cin >> n;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);}}int res = 0;for (int i = 1; i <= n; i++)res = max(res, f[i]);cout << res;return 0;
}

地宫取宝

原题链接:https://www.acwing.com/problem/content/description/1214/

限制:

  • 只能往下或往右走
  • 递增地取
  • 刚好取k个数

数据范围,很小,允许多层循环。意味着有多个维度。
f(i,j,k,c)四个维度分别表示:

  • i,j:坐标
  • k:当前取多少个
  • c:最后一件的价值,由于是递增地取,也就是当前取的最大值


取的条件,w[i,j]的值等于c。对,是等于,而不是大于。因为f(i,j,k,c)中的c指的是最后一件的值,也就是当前元素的值。
如果不选,就直接从上一步把状态k和c转移过来。
如果选了,那上一步就应该选了k-1个物品,而且最大值只能是[1,c-1],把这些状态的方案数累加。
从集合的角度分析DP,本题可以描述为:

因为涉及四层循环,四重DP,所以比较费解。更详细的分析过程在代码下面。

#include"bits/stdc++.h"using namespace std;
const int MOD = 1000000007;
int w[55][55];
int dp[55][55][13][14];
int n, m, k;int main() {cin >> n >> m >> k;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {scanf("%d", &w[i][j]);w[i][j]++;}}dp[1][1][1][w[1][1]] = 1;dp[1][1][0][0] = 1;for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (i == 1 && j == 1)continue;for (int u = 0; u <= k; u++) {for (int v = 0; v <= 13; v++) {int &val = dp[i][j][u][v];val = (val + dp[i - 1][j][u][v]) % MOD;val = (val + dp[i][j - 1][u][v]) % MOD;if (u > 0 && v == w[i][j]) {for (int c = 0; c < v; c++) {val = (val + dp[i - 1][j][u - 1][c]) % MOD;val = (val + dp[i][j - 1][u - 1][c]) % MOD;}}}}}}int res = 0;for (int i = 0; i <= 13; i++)res = (res + dp[n][m][k][i]) % MOD;cout << res;return 0;
}

边界情况:第一个元素的选取状态。由于DP是集合划分,本题有四个维度,就要分别考虑四个维度的情况。
第一个元素,坐标为(1,1)ij都只能为1.
有选和不选两种状态,k表示当前已选取的元素个数,k10两种状态。
c表示当前选取的最大值。如果选了,那么最大值就是w[i][j],如果没选,那么是没有最大值的,需要自定义一个,数据范围是[0,12],如果设置为0,由于后续判断的依据是“大于”而非“大于等于”,所以如果之后出现了0,那么将错过该位置。所有(1,1)的位置在未选取时应设置为-1或其他小于0的数。
而数组下标从0开始,那么需要添加一个偏移量1。偏移量所在的维度c应该整体偏移,因此代码中该维度的范围是[1,13],并且在读入w[i][j]时,进行了++操作。
DP的状态计算,也就是集合划分,主要是参考最后一步。
最后有两种情况,选最后也就是当前元素、不选最后也就是当前元素。
如果不选,那么目前这种方案的k和c不变,与上一步相同,状态值可以从上一步转移过来。如果是从左边过来,那就是dp[i][j][u][v]+=dp[i - 1][j][u][v]。如果是从上面过来,那就是+=dp[i][j-1][u][v].在此省略了取模操作。
为什么要+=,而不是直接用=赋值?当前DP问题的解决方案是基于集合,集合有多种划分,目前的最后一步,有多种情况,把各种情况的方案数加起来,才是集合的值。
如果选,那么需要先判断是否符合可选的条件。k表示当前取多少个,在上面的代码中用的是u。如果值为0,那说明没有取值,当然也就没选取最后一个。DP其实还是在枚举,不过是聪明的枚举,考虑了利用部分条件。c在集合中,表示当前选取的最大元素,因为是递增选取,当确定要选择当前元素时,那么c的值应该当前元素的值。
为什么要判断c==w[i][j]?因为DP的过程还是在枚举,枚举四个维度的所有可能情况。当前枚举的情况未必是符合状态表示的。
如果确定了选取当前元素。那么i,j,k三个维度容易确定:

  • ij根据从左边来还是从上边来。
  • k表示元素个数,在选之前,是有k-1个元素,只能从k-1所在的维度中选取。
  • c表示最大值,在选之前,最大值可能是1...c-1中的任何一种情况,枚举所有情况。
if (u > 0 && v == w[i][j]) {for (int c = 0; c < v; c++) {val = (val + dp[i - 1][j][u - 1][c]) % MOD;val = (val + dp[i][j - 1][u - 1][c]) % MOD;}
}

因为会多次修改dp[i][j][u][v],所有创建了对该内存位置的引用&val,对val的修改会影响指向的内存数据。
每次运算都在取模,最多只能加两个数。如果加三个再取模,有可能超出范围报错。

参考

  • https://www.acwing.com/solution/content/7116/

波动数列

原题链接:https://www.acwing.com/problem/content/1216/

第一项设为x,第二项设为x+d1,第二项设为x+d1+d2
每一个d都只有两种取值:+a,-b
n项加起来的和是snx+(n-1)d1+(n-2)d2+...+dn-1=s
原序列所有不同的方案,是由变量决定的。
第一个变量是x:属于任意整数
之后的di:只有两种取值
由于x取法太多,没有限制。并且这是一个等式,可以去掉一个变量,用n-1个变量表示被去掉的那个。

  • x = s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 ) n x=\frac {s-((n-1)d_1+(n-2)d_2+...+d_{n-1})}{n} x=ns((n1)d1+(n2)d2+...+dn1)

由于是整数序列,x也是整数,所以 s − ( ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 ) s-((n-1)d_1+(n-2)d_2+...+d_{n-1}) s((n1)d1+(n2)d2+...+dn1)应该是n的倍数。
等价于:

  • s ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 (n-1)d_1+(n-2)d_2+...+d_{n-1} (n1)d1+(n2)d2+...+dn1n同余

s可能是负数(数据范围有给),取模可能是负数。需要实现一个函数,求正余数。
如果最后一项选择+a,那么:

  • s ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + 2 d n − 2 + a (n-1)d_1+(n-2)d_2+...+2d_{n-2}+a (n1)d1+(n2)d2+...+2dn2+an同余。

系数,和,下标,的关系是:和等于n。在选择a时,a是最后一项,因此倒数第二项变成了 2 d n − 2 2d_{n-2} 2dn2,而非之前出现的 d n − 1 d_{n-1} dn1,此时a就是 d n − 1 d_{n-1} dn1
因为系数和下标发生了变动,为了便于理解,设前n-1项的和为C
对于f[i][j]这个集合,从f[i-1][C%n]状态转移过来的时候,如果选择+a,那么第一个维度,含义是选择的个数,自然+1,也就是[i]
第二个维度,就是(C+a)%n,等于j
也就是说:jC+an同余。
那么j-aCn同余。
状态转移时,第二个维度的含义是模n的余数。要求转移之前的值,也就是求C%n,那么就可以转换成求(j-a)%nj-a可能是负数,求出来的是负余数,不在数组下标范围之内。需要求正余数。
也就得到这样一个式子:dp[i][j] = (dp[i - 1][get_mod(j - a, n)] + dp[i - 1][get_mod(j + b, n)]) % MOD;其中get_mod用于求正余数。
但这个状态转移方程是无法通过AC的。
因为上文中:

  • s ( n − 1 ) d 1 + ( n − 2 ) d 2 + . . . + d n − 1 (n-1)d_1+(n-2)d_2+...+d_{n-1} (n1)d1+(n2)d2+...+dn1n同余
  • n的时候,右式有n项。

在从n-1转移到n时,第二维度值是 ( s − d n − 1 ) % n (s-d_{n-1})\%n (sdn1)%n
在从n-2转移到n-1时,第二维度值是 ( s − d n − 1 − 2 ∗ d n − 2 ) % n (s-d_{n-1}-2*d_{n-2})\%n (sdn12dn2)%n
也就是说,di前面是有系数的。
这个状态转移方程:dp[i][j] = (dp[i - 1][get_mod(j - a, n)] + dp[i - 1][get_mod(j + b, n)]) % MOD;{a,b}的系数是1只考虑了最后一步,也就是从n-1转移到n的情况。
在前n-1项状态转移时,应将系数考虑在内。带系数做减法,才能求出正确的正余数。
系数,与,下标,的和是n。因此有:

#include"bits/stdc++.h"using namespace std;
int n, s, a, b;
int dp[1010][1010];
int MOD = 100000007;int get_mod(int a, int b) {return (a % b + b) % b;
}int main() {cin >> n >> s >> a >> b;dp[0][0] = 1;for (int i = 1; i < n; i++) {for (int j = 0; j < n; j++) {dp[i][j] = (dp[i - 1][get_mod(j - (n - i) * a, n)] + dp[i - 1][get_mod(j + (n - i) * b, n)]) % MOD;}}cout << dp[n - 1][get_mod(s, n)];return 0;
}

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

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

相关文章

Rocky Linux 运维工具 ls

一、ls 的简介 ​​ls​ 用于列出当前目录下的文件和目录&#xff0c;以及它们的属性信息。通过 ​ls​命令可以查看文件名、文件大小、创建时间等信息&#xff0c;并方便用户浏览和管理文件。 二、ls 的参数说明 序号参数描述1-a显示所有文件&#xff0c;包括以 ​.​开头的…

golang学习7,glang的web的restful接口结构体传参

接口&#xff1a; //POST请求 返回json 接口传参json r.POST("/postJson", controller.PostUserInfo) 1.定义结构体 //定义结构体 type Search struct {Id intName string }2.结构体传参 //结构体传参 func PostUserInfo(c *gin.Context) {search : &Searc…

pytorch安装GPU版本 (Cuda12.1)教程

使用本教程前&#xff0c;默认您已经安装并配置好了python3以上版本 1. 去官网下载匹配的Cuda Cuda下载地址 当前最高版本的Cuda是12.1 我安装的就是这个版本 小提示&#xff1a;自定义安装可以只选择安装Cuda Runtime。Nvidia全家桶不必全部安装。把全家桶全部安装完直接系统…

VPX基于全国产飞腾FT-2000+/64核+复旦微FPGA的计算刀片

6U VPX计算板 产品简介 产品特点 飞腾计算平台&#xff0c;国产化率100% VPX-MPU6902是一款基于飞腾FT-2000/64核的计算刀片&#xff0c;主频2.2GHz&#xff0c;负责业务数据流的管控和调度。搭配自带独立显示芯片的飞腾X100芯片&#xff0c;可用于于各类终端及服务器类应用场…

【毕业设计推荐】基于MATLAB的水果分级系统设计与实现

一、课题介绍 现在商业行为中&#xff0c;在水果出厂前都需要进行质量检测&#xff0c;需要将不同等级的水果进行分级包装&#xff0c;以保证商业利益最大化。可是传统方法都是依靠人工进行检测&#xff0c;效率低下&#xff0c;主观成分大&#xff0c;并不能很好客观地评价出货…

【笔记】深度学习入门:基于Python的理论与实现(五)

卷积神经网络 卷积神经网络(Convolutional Neural Network&#xff0c;CNN) 整体结构 CNN 中新出现了卷积层(Convolution 层)和池化层(Pooling 层)&#xff0c;之前介绍的神经网络中&#xff0c;相邻层的所有神经元之间都有连接&#xff0c;这称为全 连接(fully-connected) …

Linux Seccomp 简介

文章目录 一、简介二、架构三、Original/Strict Mode四、Seccomp-bpf五、seccomp系统调用六、Linux Capabilities and Seccomp6.1 Linux Capabilities6.2 Linux Seccomp 参考资料 一、简介 Seccomp&#xff08;secure computing&#xff09;是Linux内核中的一项计算机安全功能…

4. client-go 编程式交互

Kubernetes 系统使用 client-go 作为 Go 语言的官方编程式交互客户端库&#xff0c;提供对 Kubernetes API Server 服务的交互访问。Kubernetes 的源码中已经集成了 client-go 的源码&#xff0c;无须单独下载。client-go 源码路径为 vendor/k8s.io/client-go。 开发者经常使用…

四种经典限流算法讲解

1. 固定窗口限流算法 1.1 什么是固定窗口限流算法 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是一种最简单的限流算法&#xff0c;其原理是在固定时间窗口(单位时间)内限制请求的数量。该算法将时间分成固定的窗口&#xff0c;并在每个窗…

【Java】双亲委派机制及其打破的相关知识笔记

目录 类的加载机制 类加载器 双亲委派模型机制 为什么要有双亲委派机制 如何打破双亲委派机制 类的加载机制 类加载过程是由Java虚拟机的类加载子系统完成的&#xff0c;它负责将字节码加载到内存中&#xff0c;并进行链接和初始化操作。类加载过程是Java中非常重要的一部…

六、防御保护---防火墙内容安全篇

六、防御保护---防火墙内容安全篇 一、IAE&#xff08;Intelligent Awareness Engine&#xff09;引擎二、深度检测技术(DFI和DPI&#xff09;2.1 DPI -- 深度包检测技术2.1.1 基于“特征字”的检测技术2.1.2 基于应用网关的检测技术2.1.3 基于行为模式的检测技术 2.2 DFI -- 深…

163邮箱SMTP端口号及服务器地址详细设置?

163邮箱SMTP端口号是什么&#xff1f;163邮件SMTP设置教程&#xff1f; 除了基本的邮箱账号和密码外&#xff0c;还需要了解SMTP服务器地址和端口号&#xff0c;以及相应的设置。这些设置对于确保邮件能够顺利发送至关重要。下面&#xff0c;蜂邮EDM将详细介绍163邮箱SMTP端口…

RF自动化环境安装+自动化实例解析

RF定义&#xff1a; 通用型的 自动测试框架&#xff0c; 绝大部分的软件的的自动化系统都可以采用它。 特点&#xff1a; 测试数据文件&#xff08;Test Data&#xff09;对应一个个的测试用例。测试数据文件里面使用的功能小模块叫关键字&#xff0c;由测试库&#xff08;T…

【八股文学习日记】集合概述

【八股文学习日记】集合概述 集合概述 Java 集合&#xff0c; 也叫作容器&#xff0c;主要是由两大接口派生而来&#xff1a;一个是 Collection接口&#xff0c;主要用于存放单一元素&#xff1b;另一个是 Map 接口&#xff0c;主要用于存放键值对。对于Collection 接口&#…

重铸安卓荣光——双向滑块增强

痛点&#xff1a; 公司打算做安卓软件&#xff0c;最近在研究安卓&#xff0c;打算先绘制样式 研究发现安卓并不像前端有那么多组件库&#xff0c;甚至有些基础的组件都需要自己实现&#xff0c;记录一下自己实现的组件 成品展示 上面一条是谷歌提供的双向滑块&#xff0c;下面…

使用空闲电脑免费搭建一个私人的网盘

如果你也有一台空闲电脑&#xff0c;可以使用它来搭建一个私人的网盘。 这里使用的是飞梦云网盘&#xff1b; 服务端&#xff1a;下载 服务器文件使用hash校验进行储存&#xff0c;实现重复上传的文件秒传功能。 Fuse4Ui&#xff08;虚拟分区工具&#xff09;&#xff1a;下…

精品基于SpringBoot的体育馆场地预约赛事管理系统的设计与实现-选座

《[含文档PPT源码等]精品基于SpringBoot的体育馆管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功&#xff01; 软件开发环境及开发工具&#xff1a; Java——涉及技术&#xff1a; 前端使用技术&#…

雾锁王国服务器要开服务器吗?

雾锁王国要开服务器吗&#xff1f;可以使用官方服务器&#xff0c;也可以自己搭建多人联机服务器&#xff0c;更稳定不卡&#xff0c;畅玩开黑。阿腾云分享atengyun.com给大家目前阿里云和腾讯云均提供雾锁王国服务器和一键搭建程序&#xff0c;成本26元即可搭建一台自己的雾锁…

初识Maven

介绍&#xff1a; web后端开发技术ApacheMaven是一个项目管理和构建工具&#xff0c;它基于项目对象模型&#xff08;POM&#xff09;的概念&#xff0c;通过一小段描述信息来管理项目的构建。安装&#xff1a;http://maven.apache.org/ Apache软件基金会&#xff0c;成立于19…

pdf转word文档怎么转?分享4种转换方法

pdf转word文档怎么转&#xff1f;在日常工作中&#xff0c;我们经常遇到需要将PDF文件转换为Word文档的情况。无论是为了编辑、修改还是为了重新排版&#xff0c;将PDF转为Word都显得尤为重要。那么&#xff0c;PDF转Word文档怎么转呢&#xff1f;今天&#xff0c;就为大家分享…