算法基础精选题单 动态规划(dp)(区间dp)(个人题解)

目录

前言:

正文:

  题单:【237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 (nowcoder.com)

NC50493 石子合并:

NC50500 凸多边形的划分:

NC235246 田忌赛马:

NC13230 合并回文子串:

NC16645 [NOIP2007]矩阵取数游戏:

NC207781 迁徙过程中的河流:

后记:

前言:

  这些dp对我来说就没那么简单了,但写过这些题目之后我确实对区间dp有了一个简单的认识,区间dp一般都是二维的,由第一维表示左端点,第二维表示右端点,且状态一般由区间长度小的转移而来,所以我们写dp的转移时第一层循环由len来从小枚举区间长度,第二层循环枚举区间的左端点l,右端点就可以直接表示为l+len-1(也可先枚举右端点,再从右端点往左枚举左端点),初始化和状态转移方程就得根据相应题目来了,在写这写题目时我也学会了其他的一些技巧,比如环形数组的题该如何写以及__int128的用法(用来处理一些暴long long)的情况。


正文:

  题单:【237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 (nowcoder.com)

NC50493 石子合并:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[410],dp1[410][410],dp2[410][410],pre[410];
int main(){int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];a[i+n]=a[i];}for(int i=1;i<=2*n;i++){pre[i]=pre[i-1]+a[i];}memset(dp1,127,sizeof(dp1));memset(dp2,0,sizeof(dp2));for(int j=1;j<=2*n;j++){for(int i=j;i>=1;i--){if(i==j)dp1[i][j]=dp2[i][j]=0;for(int k=i;k<j;k++){dp1[i][j]=min(dp1[i][k]+dp1[k+1][j]+pre[j]-pre[i-1],dp1[i][j]);dp2[i][j]=max(dp2[i][k]+dp2[k+1][j]+pre[j]-pre[i-1],dp2[i][j]);}//cout<<i<<" "<<j<<" "<<dp1[i][j]<<endl;}}ll ansn=0x3f3f3f3f,ansm=0;for(int i=1;i<=n;i++){ansn=min(ansn,dp1[i][i+n-1]);ansm=max(ansm,dp2[i][i+n-1]);}cout<<ansn<<endl<<ansm<<endl;return 0;
}

这题原型是比较经典的模板题,这题在原题上做了些修改,一是要求最小和最大的值,二是这是个环形的数组;对于一我们处理两个不同初值的dp数组, 分别求min,max,对于二我们则将数组扩大到2n,a[n+i]=a[i],这样枚举就能表示环形数组的所有情况,不过最后我们求答案时也要枚举所有长度为n的区间dp值。

dp的初始状态为 dp[i][j]=0(i==j )

dp的状态转移方程我们可以知道为:

dp[i][j]=dp[i,k]+dp[k+1,j]+pre[j]-pre[i-1]

其中dp[i][j]表示将从石头i到石头j合并的得分(dp1为最小,dp2为最大)(同理dp[i][k]及dp[j]),pre[j]-pre[i-1]为此次合并的得分,由前缀和表示。

由此得结果。

NC50500 凸多边形的划分:

#include<bits/stdc++.h>
using namespace std;
__int128 read(){//直接在函数里面实现读字符串操作更简洁__int128 res=0;//初始结果赋值0char scan[1005];scanf("%s",scan);for(int i=0;i<strlen(scan);i++)res*=10,res+=scan[i]-'0';//实现进位return res;//返回__int128类型
}
void print(__int128 num){//递归调用,实现从高位向低位输出if(num>9) print(num/10);putchar(num%10+'0');
}
__int128 a[55],dp[55][55];
int main(){int n;cin>>n;for(int i=1;i<=n;i++){a[i]=read();}for(int l=3;l<=n;l++){for(int i=1;i+l-1<=n;i++){int j=i+l-1;dp[i][j]=1e30;for(int k=i+1;k<j;k++){dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]);}}}print(dp[1][n]);return 0;
}

这题像是能量项链的变式。状态转移方程为下:

dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+a[i]*a[k]*a[j])

画个图大家自己体会下(可能不是很形象):

其中dp[i][j]表示将i到j点这个多边形分割后权值和的最小值(i,j在这里是一个表示首,一个表示尾),

a[i]*a[k]*a[j]为这个三角形的权值。

由于要求最小值,所以初始化为最大数,在加上数据会暴long long ,所以这里就要用到__int128了,他可以表示-2^127~2^127大小的数,但不能直接输入输出,必须采用我代码中那样的自定义函数。

NC235246 田忌赛马:

#include<bits/stdc++.h>
using namespace std;
const int N=2005;
int a[N],b[N],g[N][N],dp[N][N];
bool cmp(int n1,int n2){return n1>n2;
}
int main(){int n,ans;cin>>n;for(int i=1;i<=n;++i)cin>>a[i];for(int i=1;i<=n;++i)cin>>b[i];sort(a+1,a+n+1,cmp),sort(b+1,b+n+1,cmp);for(int i=1;i<=n;++i)for(int j=1;j<=n;++j){if(a[i]>b[j]) g[i][j]=200;else if(a[i]==b[j]) g[i][j]=0;else g[i][j]=-200;}for(int i=1;i<=n;++i){dp[i][0]=dp[i-1][0]+g[n-i+1][i];dp[i][i]=dp[i-1][i-1]+g[i][i];for(int j=1;j<i;++j) dp[i][j]=max(dp[i-1][j]+g[n-i+j+1][i],dp[i-1][j-1]+g[j][i]);}ans=dp[n][1];for(int i=2;i<=n;++i)ans=max(ans,dp[n][i]);cout<<ans<<endl;return 0;
}

首先给国王和田忌的马从小到大排序

接下来是贪心策略:

  1. 首先比最快的马,如果能赢就直接赢。
  2. 如果赢不了,就比国王和田忌最慢的马,能赢就赢。
  3. 如果最慢的马也赢不了,就用最慢的马去对国王最快的马。

设dp[i][j]表示齐王按从强到弱的顺序出马和田忌进行了i场比赛之后,从""头""取了j匹较强的马,从""尾""取了i-j匹较弱的马,所能获得的最大盈利。

其中g[i][j]表示田忌的马和齐王的马分别按照由强到弱的顺序排序之后,田忌的第 i 匹马和齐王的第 j 匹马赛跑所能取得的盈利,胜为 200 ,负为 −200 ,平为 0。

得状态转移方程为:

dp[i][j]=max(dp[i-1][j]+g[n-i+j+1][i],dp[i-1][j-1]+g[j][i]

NC13230 合并回文子串:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+5;
char a[N],b[N];
ll dp[55][55][55][55];
int main(){int t;cin>>t;while(t--){memset(dp,0,sizeof(dp));int ans=0;scanf("%s",a+1);scanf("%s",b+1);int sa=strlen(a+1),sb=strlen(b+1);for(int x=0;x<=sa;x++){for(int y=0;y<=sb;y++){for(int i=1,j=x;j<=sa;i++,j++){for(int k=1,l=y;l<=sb;k++,l++){if(x+y<=1) dp[i][j][k][l]=1;else{if(a[i]==a[j]) dp[i][j][k][l]|=dp[i+1][j-1][k][l];if(b[k]==b[l]) dp[i][j][k][l]|=dp[i][j][k+1][l-1];if(a[i]==b[l]) dp[i][j][k][l]|=dp[i+1][j][k][l-1];if(b[k]==a[j]) dp[i][j][k][l]|=dp[i][j-1][k+1][l];}if(dp[i][j][k][l])ans=max(ans,x+y);}}}}cout<<ans<<endl;}return 0;
}

初始状态只有一个字母的状态一定是回文。

假设A[i] ~ A[j]与B[k] ~ B[l]构成了一个回文串(这里设dp[i][j][k][l]),则他能转移到的区间有
1:a[i-1]==a[j+1]时 dp[i-1][j+1][k][l]
2:a[i-1]==b[l+1]时 dp[i-1][j][k][l+1]
3:b[k-1]==a[j+1]时 dp[i][j+1][k-1][l]
4:b[k-1]==b[l+1]时 dp[i][j][k-1][l+1]
那么对应的转移方程就为

if(a[i]==a[j]) dp[i][j][k][l]|=dp[i+1][j-1][k][l];

if(a[i]==b[l]) dp[i][j][k][l]|=dp[i+1][j][k][l-1];

if(b[k]==b[l]) dp[i][j][k][l]|=dp[i][j][k+1][l-1];

if(b[k]==a[j]) dp[i][j][k][l]|=dp[i][j-1][k+1][l];

NC16645 [NOIP2007]矩阵取数游戏:

#include<bits/stdc++.h>
using namespace std;
__int128 read(){//直接在函数里面实现读字符串操作更简洁__int128 res=0;//初始结果赋值0char scan[1005];scanf("%s",scan);for(int i=0;i<strlen(scan);i++)res*=10,res+=scan[i]-'0';//实现进位return res;//返回__int128类型
}
void print(__int128 num){//递归调用,实现从高位向低位输出if(num>9) print(num/10);putchar(num%10+'0');
}
typedef long long ll;
__int128 pow2(__int128 a,__int128 b){__int128 res=1;while(b){if(b&1)res*=a;a*=a,b/=2;}return res;
}
__int128 a[100][100];
__int128 dp[100][100][100];
int main(){ll n,m;__int128 ans=0;cin>>n>>m;for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){a[i][j]=read();}}for(int k=1;k<=n;k++){for(int len=1;len<=m;len++){for(int i=1;i<=m,i+len-1<=m;i++){int j=i+len-1;if(i==j)dp[i][j][k]=a[k][i]*pow2(2,m-len+1);else dp[i][j][k]=max(dp[i+1][j][k]+a[k][i]*pow2(2,m-len+1),dp[i][j-1][k]+a[k][j]*pow2(2,m-len+1));//cout<<i<<" "<<j<<" "<<k<<" "<<dp[i][j][k]<<endl;}}//cout<<dp[1][m][k]<<endl;ans+=dp[1][m][k];}print(ans);return 0;
}

(自己独立思考写出来的,很有成就感)

因为每次只能取行首或者行尾,所以每行取得顺序都是独立的,由此可以从求最大的得分和转换成求每行的最大得分和。然后用区间dp计算每一行,状态转移方程为:

dp[i][j][k]=max(dp[i+1][j][k]+a[k][i]*pow2(2,m-len+1),dp[i][j-1][k]+a[k][j]*pow2(2,m-len+1))

k表示第几行.i,j表示区间,a[k][i]*pow2(2,m-len+1)表示取该数的得分,根据len来判断这次为第几次取数。

NC207781 迁徙过程中的河流:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100005],dp[100005];
int main(){int n;cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}sort(a+1,a+n+1);dp[1]=a[1];dp[2]=a[2];for(int i=3;i<=n;i++){dp[i]=min(dp[i-1]+a[1]+a[i],dp[i-2]+a[1]+2*a[2]+a[i]);}cout<<dp[n];return 0;
}

说实话这题更像是线性dp,从题目中规律可以看出来在某个人要过河的时候要么是最快的那个人来接他,要么是还剩下两个让最快的把船开回来然后让这两个过去,之后让第二快的把船开过来,全部过去。这两个在题目中的样例里面都有体现。转移方程为:

dp[i]=min(dp[i-1]+a[1]+a[i],dp[i-2]+a[1]+2*a[2]+a[i])

后记:

  题对我确实很难,不过我也还算收获满满,写博客过程中也相当于是把写题过程在梳理了一遍,希望这篇博客不只能帮助到我自己吧。

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

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

相关文章

【机器学习】klearn基础教程

scikit-learn&#xff08;通常缩写为sklearn&#xff09;是一个用于Python编程语言的强大机器学习库。它提供了各种分类、回归、聚类算法&#xff0c;以及数据预处理、降维和模型评估的工具。以下是sklearn的基础教程&#xff0c;帮助你开始使用它&#xff1a; 1. 安装scikit-…

神经网络的卷积操作中不同的卷积核和不同的填充获得同样的输出

神经网络的卷积操作中不同的卷积核和不同的填充获得同样的输出 nn.Conv2d(in_channels,out_channels,kernel size, stridel, padding-0,dilationl,groupsl,biasTrue,padding_modezeros,deviceNone, dtypeNone)nn.Conv2d卷积输人数据的格式是(batch_size,C&#xff0c;H&#x…

排序算法、堆排序、大顶堆、小顶堆、手写快排-215. 数组中的第K个最大元素、2336. 无限集中的最小数字

目录 215. 数组中的第K个最大元素 题目链接及描述 题目分析 堆排序分析 堆排序代码编写 快排分析 快排代码编写 2336、无限集中的最小数字 题目链接及描述 题目分析 代码编写 215. 数组中的第K个最大元素 题目链接及描述 215. 数组中的第K个最大元素 - 力扣&#…

python函数——基础

一、函数基础 函数体&#xff08;功能代码块&#xff09; 函数是执行特定任务的代码块&#xff0c;通过调用函数名并传递参数&#xff08;如果有的话&#xff09;来执行这些任务。 函数的参数 参数列表由一系列参数组成&#xff0c;使用逗号分隔。实参&#xff1a;调用函数时…

C#.net6.0语言+B/S架构+前后端分离 手术麻醉信息管理系统源码

C#.net6.0语言&#xff0b;B/S架构前后端分离 手术麻醉信息管理系统源码 什么是手术麻醉信息管理系统 满足医院等级评级需求 满足电子病历评级需求 满足科室需求 术前 1、患者术前评估/诊断 2、术前讨论制定手术方案 3、手术准备 4、术前准备 术中 1、送手术室 2、麻…

数据库浅识及MySQL的二进制安装

数据库基础概念与MySQL二进制安装与初始化 使用数据库的必要性 数据库可以结构化储存大量数据信息&#xff0c;方便用户进行有效的检索访问 有效的保持数据信息的一致性&#xff0c;完整性&#xff0c;降低数据冗余 可以满足应用的共享和安全方面的要求 数据库基本概念 数据…

C语言经典算法题第一题

题目&#xff1a;古典问题&#xff1a;有一对兔子&#xff0c;从出生后第3个月起每个月都生一对兔子&#xff0c;小兔 子长到第三个月后每个月又生一对兔子&#xff0c;假如兔子都不死&#xff0c;问每个月的兔子总数 为多少&#xff1f; #include <stdio.h>int main() …

Swagger文档渲染,将Filter过滤器中抛出的异常错误消息返回前端显示

一、应用场景 在swagger通过/v2/api-docs获取到后端接口数据后&#xff0c;使用过滤器对/v2/api-docs请求进行拦截&#xff0c;然后对获取到的数据进行处理&#xff0c;当在处理过程中对Filter过滤器中抛出的异常错误消息进行捕获&#xff0c;并将捕获到的异常错误消息返回到前…

【SpringBoot3.x】自定义开发通用SDK

1. 前言 相信大家学习SpringBoot到现在&#xff0c;使用Maven构建项目时&#xff0c;会在pom.xml文件中引入各种各样的依赖&#xff0c;那么我们如何将自己常用的一些工具类库进行封装成starter或者SDK供其他项目使用呢&#xff0c;本博客就会带着大家一步一步创建自定义的SDK…

开源TinyFSM状态机适用于嵌入式工业平台吗?

文章目录 引言基于传统 C 实现的状态机TinyFSM 实现的对比现代 C 实现的状态机性能对比TinyFSM 性能测试传统 C 性能测试现代 C 性能测试 工业Misra C编程标准TinyFSM 的优缺点分析结论 引言 TinyFSM是一个为C设计的轻量级有限状态机开源库库。 在嵌入式系统开发中&#xff0c…

倍思突破氮化镓快充技术,为用户带来安全舒适体验

氮化镓,这个化学式为GaN的化合物,其高热稳定性和化学稳定性使其在多种极端环境中都能保持优良的性能,从而为其在电子器件领域的应用奠定了坚实的基础。 2018年前后开始,氮化镓快充充电器进入国内市场。作为第三代半导体材料的代表,氮化镓具有宽禁带的特性,其禁带宽度远大于传统…

Python连接Redis(简单连接、连接池连接、存取数据示例)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

数据中心:AI范式下的内存挑战与机遇

在过去的十年里&#xff0c;数据中心和服务器行业经历了前所未有的扩张&#xff0c;这一进程伴随着CPU核心数量、内存带宽(BW)&#xff0c;以及存储容量的显著增长。这种超大规模数据中心的扩张不仅带来了对计算能力的急剧需求&#xff0c;也带来了前所未有的内存功率密度挑战&…

Docker 从安装到使用的完整指南

目录 什么是 DockerDocker 的核心概念 镜像容器DockerfileDocker Compose Docker 的安装 在 Windows 上安装 Docker在 macOS 上安装 Docker在 Linux 上安装 Docker Docker 的基本使用 启动第一个容器管理容器构建自定义镜像使用 Docker Compose 高级主题 Docker 网络Docker 数…

适合孩子学习用什么的落地灯?五款精品护眼大路灯分享

适合孩子学习用什么的落地灯&#xff1f;说到护眼落地灯&#xff0c;都会出现两种呼声&#xff1a;一种是认为是“智商税”&#xff0c;而另外一种则是妥妥的照明神器&#xff01;护眼大路灯到底是哪种定义呢&#xff1f;贵的护眼灯一定好吗&#xff1f; 这么年&#xff0c;护…

区块链技术:重塑金融市场监管的新引擎

一、引言 随着金融市场的不断发展和创新&#xff0c;监管面临的挑战也日益严峻。传统的监管模式已难以满足现代金融市场的需要&#xff0c;而区块链技术的出现为金融市场监管带来了新的机遇。本文将探讨区块链技术在金融市场监管中的作用&#xff0c;以及它如何重塑监管模式&a…

深入研究websocket直播中signature这个参数怎么来的,模拟自己生成一个

上一节课我们已经找到了生成signature这个字段的代码位置&#xff0c;就是这个B函数&#xff0c;嗯......听起来好像有点奇怪&#xff0c;但是它确实叫B啊&#xff0c;笑死。不管了&#xff0c;看一下里面的逻辑是啥。 注意e参数的内容是&#xff1a; {"app_name":…

【Academy】API测试API testing

API测试API testing 概述API侦察API文档发现API文档使用机器可读的API文档 标识API端点与API端点交互确定支持的HTTP方法确定支持的内容类型使用Intruder查找隐藏端点 查找隐藏参数大量分配漏洞识别隐藏参数测试大规模分配漏洞 防止API中的漏洞服务端参数污染测试查询字符串中的…

作业管理系统

摘 要 随着网络的发展&#xff0c;信息化时代的到来&#xff0c;在教学工作的过程中作用越来越明显&#xff0c;作业的及时发布&#xff0c;学生的及时提交&#xff0c;以及通过网上的批改和评分&#xff0c;都大大促进教学质量的发展&#xff0c;充分的利用网络来加强管理&am…

用类来实现输入和输出时间(时:分:秒)

编写程序&#xff1a; 运行结果&#xff1a; 程序分析&#xff1a; 这是一个很简单的例子。类Time中只有数据成员&#xff0c;而且它们被定义为公用的&#xff0c;因此可以在类的外面对这些成员进行操作。t1被定义为Time类的对象。在主函数中向t1对象的数据成员输入用户…