保研考研机试攻略:第八章——动态规划(1)

2024-08-22 21:28

本文主要是介绍保研考研机试攻略:第八章——动态规划(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🍨🍨🍨这一章,我们来看一些常见的动态规划题型,包括递推求解、最大子段和、最长上升子序列(LIS)、最长公共子序列(LCS)、背包类问题、记忆化搜索、字符串相关的动态规划等内容。希望能帮助大家更好的掌握计算机考研机试中所涉及到的动态规划问题。加油!( •̀ ω •́ )✧

目录

🧊🧊🧊8.1 递推求解

🥥例题:DreamJudge 1413

🥥练习题目:

DreamJudge 1197 吃糖果

DreamJudge 1033 细菌的繁殖

DreamJudge 1726 不连续 1 的子串数量

🧊🧊🧊8.2 最大子段和

🥥题型总结:

🥥例题:DreamJudge 1172

🥥例题:DreamJudge 1642

🥥练习题目:

DreamJudge 1334 最大连续子序列 🍰

DreamJudge 1703 最大子串和

🧊🧊🧊8.3 最长上升子序列(LIS)

🥥例题:DreamJudge 1257

🥥练习题目:

DreamJudge 1256 拦截导弹 🍰

DreamJudge 1253 合唱队形

DreamJudge 1836 最长递减子序列


🧊🧊🧊8.1 递推求解

首先,什么是动态规划呢?

动态规划就是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或叫分治)的方式去解决。其实就是分解问题,分而治之。

说一个我们众所周知的例子——爬楼梯上楼问题:N 阶楼梯上楼,一次可以走两阶或一阶,问有多少种上楼方式。

按照动态规划思想,我们来分析下问题:每次都有两种跳法,分别是走一阶或两阶。如果当前是第 n 个台阶,那么跳法就是第(n-1)台阶的跳法数加上第(n-2)台阶的跳法数。如果换成公式是 F(n) = F(n-1)+F(n-2)。F(n)代表第 n 阶台阶的跳法的数量。

这就相当于找到了一个递推公式,然后来进行计算。

🥥例题:DreamJudge 1413

#include <stdio.h>
int main() {int i,N;long long a[90];while(scanf("%d",&N) != EOF) {a[1]=1;a[2]=2;for(i=3;i<=N;i++)a[i]=a[i-1]+a[i-2];printf("%lld\n",a[N]);}return 0;
}

🥥练习题目:

DreamJudge 1197 吃糖果

#include<bits/stdc++.h>
using namespace std;
int main()
{int n;while(cin>>n){int dp[50]={0};dp[1]=1;dp[2]=2;for(int i=3;i<=n;i++) dp[i]=dp[i-1]+dp[i-2];cout<<dp[n]<<endl;}return 0;
}

DreamJudge 1033 细菌的繁殖

#include<bits/stdc++.h>
using namespace std;
int main()
{int n,day,cnt=1;cin>>n;int dp[1010]={0};dp[0]=dp[1]=1;for(int i=2;i<=1005;i++) {dp[i]=dp[i-1]+4*cnt;cnt++;}while(n--){cin>>day;cout<<dp[day]<<endl;}return 0;
}

DreamJudge 1726 不连续 1 的子串数量

#include<bits/stdc++.h>
using namespace std;
int main()
{int n;int dp[100][2]={0};cin>>n;dp[1][0]=1;//第1位是0dp[1][1]=1;//第1位是1for(int i=2;i<=n;i++){dp[i][1]+=dp[i-1][0];//当前位为1,那么前一位得是0才能不连续dp[i][0]+=dp[i-1][1]+dp[i-1][0];//当前位为0,那么前一位可以是0也可以是1}cout<<dp[n][1]+dp[n][0]<<endl;return 0;
} 

🧊🧊🧊8.2 最大子段和

🥥题型总结:

1、直接求最大子段和的值

2、要求记录最大子段值的起始坐标和终止坐标或者起始值和终止值

3、首尾可以连接成环的最大子段和的值

🥥例题:DreamJudge 1172

由于 N 很大,所以我们不能暴力的枚举起点和终点,使用动态规划的思想,我们发现一个特点,即我们只需要从一个正数开始不断的累加,然后更新其中的最大值即可。

#include <bits/stdc++.h>
using namespace std;
int dp[1000010];
int a[1000010];
long long maxx;
int main() {int n ;while(cin >> n){for(int i = 0; i < n; i++)cin >> a[i];dp[0] = a[0];maxx = a[0];//最小的情况就是不选那么答案就是 0for(int i = 1; i < n; i++){dp[i] = max(dp[i-1] + a[i], a[i]);if(maxx < dp[i]) {//如果累加到更大的值则更新maxx = dp[i];}}cout << maxx << endl;}return 0;
}

接下来看一道进阶应用例题:

🥥例题:DreamJudge 1642

首先,看到题目的数据范围,第一反应就是这个题只能用 O(n)时间复杂度的算法。

那么可供我们选择的就不多了。一种方式是深挖题目,可以发现其实题目就是让我找出一段 0 比 1 的个数多的最多的区间,我们可以用满分篇中讲到的毛毛虫算法来进行不断蠕动解决。那么现在我们有没有别的解决方法呢?和本节讲到的最大子段和有什么关系吗?

我们把 01 字符串的 0 变成 1,1 变成-1,然后构成一个 1 和-1 的字符串。对这样一个字符串去求它的最大子段和即可,最后再把 1 的个数加上就是最终的答案。

#include <stdio.h>
#include <string.h>
int dp[10000005];
int a[10000005];
char s[10000005];
int _max(int x, int y) {return x > y ? x : y;
}
int main() {int n;while (scanf("%d", &n) != EOF) {scanf("%s", s);for (int i = 0; i < n; i++) {if (s[i] == '0') a[i] = 1;else a[i] = -1;}memset(dp, 0, sizeof(dp));dp[0] = a[0];int maxx = 0;//可以为空for (int i = 0; i < n; i++) {dp[i] = _max(dp[i - 1] + a[i], a[i]);if (maxx < dp[i]) maxx = dp[i];}int ans = 0;//统计 1 的个数for (int i = 0; i < n; i++)if (s[i] == '1') ans++;printf("%d\n", maxx + ans);}return 0;
}

🥥练习题目:

DreamJudge 1334 最大连续子序列 🍰

输入样例:

6
-2 11 -4 13 -5 -2
10
-10 1 2 3 4 -5 -23 3 7 -21
6
5 -8 3 2 5 0
1
10
3
-1 -5 -2
3
-1 0 -2
0
//摘自N诺用户:为欢几何
#include<bits/stdc++.h>
using namespace std;
long long dp[1000010];
long long a[1000010];
long long maxm;//记录区间最大值
long long s=0,d=0;//记录区间始末位置
long long temp_start=0;
int main(){long long n;while(cin>>n){if(n==0) break;int sum=0;for(long long i=0;i<n;i++){cin>>a[i];if(a[i]<0) sum++;}if(n==1){cout<<a[0]<<" "<<a[0]<<" "<<a[0]<<endl;}else if(sum==n){cout<<"0 "<<a[0]<<" "<<a[n-1]<<endl;}else{dp[0]=a[0];maxm=dp[0];//最小的情况非空,那么就是某一个值s=1;d=1;for(long long i=1;i<n;i++){if(dp[i-1]+a[i]<a[i]){dp[i]=a[i];temp_start=i;}else{dp[i]=dp[i-1]+a[i];}if(dp[i]>maxm){maxm=dp[i];s=temp_start;d=i;}}cout<<maxm<<" "<<a[s]<<" "<<a[d]<<endl;}}return 0;
}

DreamJudge 1703 最大子串和

#include<bits/stdc++.h>
using namespace std;
int main(){int n;int a[110],dp[110];int s,d;while(cin>>n){for(int i=0;i<n;i++) cin>>a[i];dp[0]=a[0];for(int i=1;i<n;i++) dp[i]=max(dp[i-1]+a[i],a[i]);//如果加上我比我自己更大,那么选我入区间是对的选择int temp=dp[0];for(int i=1;i<n;i++){//找到dp最大的位置,区间结尾就是这里if(temp<dp[i]){temp=dp[i];d=i;}}s=d;int ans=temp;while(true){//向前找起点temp-=a[s];if(temp==0) break;//起点s--;}for(int i=s;i<=d;i++){cout<<a[i]<<" ";}cout<<endl<<ans<<endl;}return 0;
}

🧊🧊🧊8.3 最长上升子序列(LIS)

最长上升子序列(Longest Increasing Subsequence, 简称 LIS)是 dp 中比较经典的一个算法模型,它有一种朴素的算法 O(n^2)和一种优化版的算法 O(nlogn)实现, 通过它, 我们可以进一步了解 dp 的思想。

我们先来看 O(n^2)的算法 LIS_nn()

首先确定状态转移方程 dp[i]代表以第 i 项为结尾的 LIS 的长度:

dp[i] = max(dp[i], max(dp[j]) + 1)     if j < i and a[j] < a[i]

根据上面的状态转移方程可以写出下面的代码:

int LIS_nn() {int ans = 0;for (int i = 1; i <= n; ++i) {dp[i] = 1;for (int j = 1; j < i; ++j) {if (a[j] < a[i]) { //要满足上升的条件dp[i] = max(dp[i], dp[j] + 1);}}ans = max(ans, dp[i]);}return ans;
}

刚才的算法是否还有优化的空间呢?

在刚才的内层 for 我们从前往后找一个最大的 LIS 值,仔细想一下是否可以发现这个值一定是单调递增的呢? 由于这个值是单调递增的,所以我们就没必要使用从前往后遍历的方法,可以使用二分查找来

优化这个寻找的过程。

于是可以实现O(nlogn)算法的LIS_nlgn()函数:

int LIS_nlgn() {int len = 1;dp[1] = a[1];for (int i = 2; i <= n; ++i) {if (a[i] > dp[len]) {dp[++len] = a[i];} else {int pos = lower_bound(dp, dp + len, a[i]) - dp;dp[pos] = a[i];}}return len;
}

上面的代码是求最长上升子序列的长度,也可以求最长上升子序列的累加值。

🥥例题:DreamJudge 1257

 将上面的求长度的 LIS 模板稍作修改即可得到求累加和的 LIS。

#include <bits/stdc++.h>
using namespace std;
int dp[1001], a[1001], n;
int LIS_nn() {int ans = 0;for (int i = 1; i <= n; ++i) {dp[i] = a[i];for (int j = 1; j < i; ++j) {if (a[j] < a[i]) {dp[i] = max(dp[i], dp[j] + a[i]);}}ans = max(ans, dp[i]);}return ans;
}
int main() {while (cin >> n) {for (int i = 1; i <= n; ++i) {cin >> a[i];}cout << LIS_nn() << endl;}return 0;
}

🥥练习题目:

DreamJudge 1256 拦截导弹 🍰

//摘自N诺用户:Happy0111 
#include<bits/stdc++.h>
using namespace std;
const int maxn=30;
long long  a[maxn];
long long  dp[maxn];
int k;int LIS(){int len=1;dp[len]=a[1];for(int i=2;i<=k;i++){if(dp[len]<=a[i]){dp[++len]=a[i];}else{int pos=upper_bound(dp+1,dp+len,a[i])-dp;dp[pos]=a[i];}}return len;
}
int main(){while(scanf("%d",&k)!=EOF){if(k==0){printf("0\n");continue;}for(int i=1;i<=k;i++){scanf("%lld",&a[i]);a[i]=-a[i];}int ans=LIS();printf("%d\n",ans);}
}

DreamJudge 1253 合唱队形 🍰

//摘自N诺用户:fxl
#include <bits/stdc++.h>
using namespace std;
int n,a[105],dp_h[105],dp_t[105];
int main(){while(cin>>n){for(int i=0;i<n;i++){cin>>a[i];}//从前往后找以a[i]结尾的最长上升子序列,存入dp_h for(int i=0;i<n;i++){dp_h[i]=1;for(int j=0;j<i;j++){if(a[j]<a[i]){dp_h[i] = max(dp_h[j]+1,dp_h[i]);}}} //从后往前找以a[i]结尾的最长上升子序列,存入dp_t for(int i=n-1;i>=0;i--){dp_t[i]=1;for(int j=i+1;j<n;j++){if(a[j]<a[i]){dp_t[i] = max(dp_t[j]+1,dp_t[i]);}}} int maxn=0;for(int i=0;i<n;i++){maxn=max(maxn,dp_h[i]+dp_t[i]);}maxn=maxn-1;//a[i]前后都被计算了,多算了一次所以要减1 cout<<n-maxn<<endl; }return 0;
}

DreamJudge 1836 最长递减子序列 🍰

用deque单调队列暴力只能过20%的数据

#include<bits/stdc++.h>
using namespace std;
int main() {int n,m,maxlen=1;int a[105],b[105],dp[105];//a记录原始数据,b记录最长递减序列,dp记录到当前位置的最长递减序列长度cin>>n;for(int i=1;i<=n;i++){//输入数据cin>>a[i];dp[i]=1;}for(int i=1;i<=n;i++){//更新dpfor(int j=i;j>0;j--)if(a[j]>a[i]){//如果我的前边有人比我大,那么我就可以放到那个数的后边(+1的由来,1是我)dp[i]=max(dp[j]+1,dp[i]);}maxlen=max(maxlen,dp[i]);//每次到一个新位置就更新一次}	m=maxlen;memset(b,0,sizeof(b));for(int i=n;i>0;i--){//倒着遍历一遍,找到各个数并放入b数组if(dp[i]==m) b[m--]=a[i];for(int j=i+1;j<=n;j++){if(dp[i]==dp[j]&&a[i]>b[dp[i]+1])//?没看懂b[dp[i]]=a[i];}}for(int i=1;i<=maxlen;i++) cout<<b[i]<<" ";//输出最长递减序列cout<<endl;return 0;
}

这个代码里有个没看懂的点,有没有懂的uu帮忙解答一下,谢谢!🌹🌹🌹

创作不易,点个赞吧~点赞收藏不迷路,感兴趣的宝子们欢迎关注该专栏~

勤奋努力的宝子们,学习辛苦了!宝子们可以收藏起来慢慢学哦~🌷🌷🌷休息下,我们下部分再见👋( •̀ ω •́ )✧~

这篇关于保研考研机试攻略:第八章——动态规划(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097440

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

第10章 中断和动态时钟显示

第10章 中断和动态时钟显示 从本章开始,按照书籍的划分,第10章开始就进入保护模式(Protected Mode)部分了,感觉从这里开始难度突然就增加了。 书中介绍了为什么有中断(Interrupt)的设计,中断的几种方式:外部硬件中断、内部中断和软中断。通过中断做了一个会走的时钟和屏幕上输入字符的程序。 我自己理解中断的一些作用: 为了更好的利用处理器的性能。协同快速和慢速设备一起工作

电脑桌面文件删除了怎么找回来?别急,快速恢复攻略在此

在日常使用电脑的过程中,我们经常会遇到这样的情况:一不小心,桌面上的某个重要文件被删除了。这时,大多数人可能会感到惊慌失措,不知所措。 其实,不必过于担心,因为有很多方法可以帮助我们找回被删除的桌面文件。下面,就让我们一起来了解一下这些恢复桌面文件的方法吧。 一、使用撤销操作 如果我们刚刚删除了桌面上的文件,并且还没有进行其他操作,那么可以尝试使用撤销操作来恢复文件。在键盘上同时按下“C

动态规划---打家劫舍

题目: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。 思路: 动态规划五部曲: 1.确定dp数组及含义 dp数组是一维数组,dp[i]代表

软考系统规划与管理师考试证书含金量高吗?

2024年软考系统规划与管理师考试报名时间节点: 报名时间:2024年上半年软考将于3月中旬陆续开始报名 考试时间:上半年5月25日到28日,下半年11月9日到12日 分数线:所有科目成绩均须达到45分以上(包括45分)方可通过考试 成绩查询:可在“中国计算机技术职业资格网”上查询软考成绩 出成绩时间:预计在11月左右 证书领取时间:一般在考试成绩公布后3~4个月,各地领取时间有所不同

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

代码随想录冲冲冲 Day39 动态规划Part7

198. 打家劫舍 dp数组的意义是在第i位的时候偷的最大钱数是多少 如果nums的size为0 总价值当然就是0 如果nums的size为1 总价值是nums[0] 遍历顺序就是从小到大遍历 之后是递推公式 对于dp[i]的最大价值来说有两种可能 1.偷第i个 那么最大价值就是dp[i-2]+nums[i] 2.不偷第i个 那么价值就是dp[i-1] 之后取这两个的最大值就是d

《数据结构(C语言版)第二版》第八章-排序(8.3-交换排序、8.4-选择排序)

8.3 交换排序 8.3.1 冒泡排序 【算法特点】 (1) 稳定排序。 (2) 可用于链式存储结构。 (3) 移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n较大时, 此算法不宜采用。 #include <stdio.h>#include <stdlib.h>#define MAXSIZE 26typedef int KeyType;typedef char In

数学建模笔记—— 非线性规划

数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中有一个或几个非线性函数的最优化问题的方法。运筹学的一个重要分支。2

机试算法模拟题 服务中心选址

题目描述 一个快递公司希望在一条街道建立新的服务中心。公司统计了该街道中所有区域在地图上的位置,并希望能够以此为依据为新的服务中心选址:使服务中心到所有区域的距离的总和最小。 给你一个数组positions,其中positions[i] = [left, right] 表示第 i 个区域在街道上的位置,其中left代表区域的左侧的起点,right代表区域的右侧终点,假设服务中心的位置为loca