C语言算法动态规划板子题汇总
本篇博客仅为对动态规划基础问题的状态转移方程进行求解,然后给出对应的注释代码,有关题目的具体内容可在算法导论或网络上进行查看
目录
1.钢管切割(最小值)
2.两条流水线调度
3.多条流水线调度
4.最长上升子序列
5.矩阵链乘
6.OBST
内容
1.钢管切割
实现解释:
先设数组price[i]存储着i长度钢管切割后的最小值,p[i]存储着i长度钢管不切割的值,price数组既是本问题的dp数组。
经过分析可知状态转移方程为:
price[0] = 0;
price[i] = min(p[1]+price[i-1],p[2]+price[i-2],...p[i-1]+price[1],p[i]);
因为price[i]已经是当前情况下的最小值了,所以只需要遵循转移方程进行代码的完善即可。
若要计算最大值只需在计算前设置最大值为负数(保证最小),然后在判断递归判断小于即可。
坑点:
初始化和状态转移方程的书写
完整代码:
//钢管切割最小值
#include<iostream>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
int length,MIN;//分别为长度和最小值
while(cin >> length)
{
int p[length+];
for(int i = ;i<=length;i++)
cin >> p[i];//依据题意,i长度的价值 int price[length+];//保存每段价值
price[] = ;//会使用到price[0],防止出错初始化 for(int i = ;i<=length;i++)
{
MIN = ;//获得最小值时需要设为最大值
for(int j = ;j<=i;j++)
{
if(MIN > p[j]+price[i-j])
//不断将i长度切割为前j部分和后i-j部分
//以找到i长度切割后的最小值
{
MIN = p[j] + price[i-j];//替换最小值
price[i] = MIN;
}
//状态转移方程介绍见实现解释
}
}
cout << price[length] << '\n';
//输出最长部分(i)切割后的最小值
}
return ;
}
2.两条流水线调度
实现解释:
实现方法即得出状态转移方程后完善即可,设a[][i]存储着第一二条线上各家的时间花费,t[][i]存储着i处进行线路切换的花费,f[][i]存储着各线在i处的最小花费。
则对每一个f[][i]应有如下的转移方程:
f[0][1] = a[0][1];
f[1][1] = a[1][1];
f[0][i] = min(f[0][i-1]+a[0][i],f[1][i-1]+t[1][i-1]+a[0][i]);
f[1][i] = min(f[1][i-1]+a[1][i],f[0][i-1]+t[0][i-1]+a[1][i]);
前两个为初始定义,后两个为具体的转换。
min函数中前一个值表示直接在当前这条线的前一个结点前进的花费,后一个表示从另一条线的前一个结点先转移过来后再前进的花费。此时只有两条线因此只有这两种情况,所以比较后便可得出到达该线该位置的最小花费。
多条线的情况可参考:题解:说好的ALS呢?(后续添加链接)
最后比较到达n处(最后一个家)哪条线的时间花费最小即可。
坑点:
注意多次输入后数组的初始化,以及状态转移方程的正确转化即可
完整代码:
//流水线调度基本问题
#include<iostream>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
int n;
int fend;
int i;
while(cin >> n)
{
int a[][n+],t[][n];
//两条线上每一家花费的时间
for(i = ;i<=n;i++)
cin >> a[][i];
for(i = ;i<=n;i++)
cin >> a[][i]; //两条线不同位置转移的时间花费
for(i = ;i<n;i++)
cin >> t[][i];
for(i = ;i<n;i++)
cin >> t[][i]; //存储两条线不同位置的最小时间
int f[][n+];
//初始化防止错误
f[][] = a[][];
f[][] = a[][]; for(i = ;i<=n;i++)
{
//具体状态转移方程介绍见实现解释
if(f[][i-]+a[][i]<f[][i-]+t[][i-]+a[][i])
f[][i] = f[][i-]+a[][i];
else
f[][i] = f[][i-]+t[][i-]+a[][i]; if(f[][i-]+a[][i]<f[][i-]+t[][i-]+a[][i])
f[][i] = f[][i-]+a[][i];
else
f[][i] = f[][i-]+t[][i-]+a[][i];
}
//计算两条线分别的最后时间花费得最小值
if(f[][n]<f[][n])
fend = f[][n];
else
fend = f[][n];
cout << fend << '\n';
}
return ;
}
3.多条流水线调度
实现解释:
相比两条线的其实只是需要多判断几次
设a[i][j]存储着第i条线上第j个位置的花费,t[i][j]存储着第i条线到第j条线的调度费用,f[i][j]存储着i条线在j处的最小花费。
则对每一个f[][i]应有如下的转移方程:
第一个站台:f[i][1] = a[i][1];
其余站台:f[i][j] = min(f[i][j-1]+t[i][i]+a[i][j],f[1][j-1]+t[1][i]+a[i][j],...,f[n][j-1]+t[n][i]+a[i][j]);
注意站台间转移时需要考虑自己调度到自己的情况(视题意而定)
min函数中前一个值表示直接在当前这条线的前一个结点前进的花费,后面则表示从其他线的前一个结点先转移过来后再前进的花费,一次比较后便可得出到达当前线该位置的最小花费。
最后比较到达n处(最后一个站台)哪条线的时间花费最小即可。
坑点:
注意按照题意判断是否要考虑自身的调度t[i][i]即可
完整代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
ios::sync_with_stdio(false);
int num,n;
int fend;
int i,j;
int tempf;
while(cin >> num >> n)//分别为线路个数和站台个数
{
int a[num][n+],t[num][num];
for(i = ;i<num;i++)
for(j = ;j<=n;j++)
cin >> a[i][j];//i条线j站台的花费 for(i = ;i<num;i++)
for(j = ;j<num;j++)
cin >> t[i][j];//i条线到j条线的调度费用 int f[num][n+];//一维为当前线路,二维为站台位置
for(i = ;i<num;i++)
{
f[i][] = a[i][];//每条线第一个站台的最小花费就是第一个
}
for(i = ;i<=n;i++)
{
for(j = ;j<num;j++)
{
tempf = ;//为了找到最小值先设定最大的
for(int k = ;k<num;k++)
{//循环判断所有条道路的情况
if(tempf > f[k][i-]+t[k][j]+a[j][i])
{
tempf = f[k][i-]+t[k][j]+a[j][i];
}
}
f[j][i] = tempf;//存储j条线在i站台的最小值
}
}
tempf = f[][n];
for(i = ;i<num;i++)
{
if(tempf > f[i][n])
{
tempf = f[i][n];
}
}
fend = tempf;//得到最大值
cout << fend << '\n';
}
return ;
}
4.最长上升子序列
实现解释:
这里介绍的其实是优化后的方案,即只存储长度,而不是以dp[i][j]的形式存储ij之间的最长长度,不过也是很好理解的,所以就直接分享这一篇吧。
a[i]存储总序列的内容,dp[i]表示以i为结尾的最长子序列长度
那么首先由dp[i]开始一定是1(自己是一个序列)
后面的状态转移方程即:
dp[i] = max(dp[j])+1(j<i&&a[j]<a[i])
解释:由于是上升序列,而且dp[i]是以i结尾的最长长度,因此长度增加时有两个条件:新的数字在旧数字的后面,新的数字大于旧的数字(新数字:dp[i],旧数字:dp[j]),也是唯一的转移方程。
坑点:
注意最后获取最长序列时,不能直接dp[n]输出,因为可能是在序列中间有最长的上升子序列,也需要循环判断。
完整代码:
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
int a[n],dp[n];
for(int i = ;i<n;i++)
{
cin >> a[i];
dp[i] = ;//自身一定是一个长度的序列
}
for(int i = ;i<n;i++)
{
for(int j = ;j<i;j++)
{
if(a[i] > a[j])//因为是上升,所以需要只有比前面的值大才可能形成最长上升子序列
{
if(dp[i] < dp[j]+) dp[i] = dp[j] + ;//记录i处最长的上升序列长度
//即前面的序列长度最大长度+1即是i处的最大长度
}
}
}
int max = dp[];//不一定最后一个是最长的,因此需要获取最大值
for(int i = ;i<n;i++)
{
if(max < dp[i]) max = dp[i];
}
cout << max << '\n';
return ;
}
5.矩阵链乘
实现解释:
数据介绍:m[0]存储第一个矩阵的行数,m[i]存储第i个矩阵的列数和第i+1个矩阵的行数
num[i][j]记录第i个矩阵和第j个矩阵之间的最小计算次数
cut仅是记录切割位置所用cut[i][j]表示第ij个矩阵之间的最优切割位置
则按照矩阵乘法的知识,两个矩阵相乘的计算次数便可直接由以下的状态转移方程得到:
i=j时num[i][j] = 0
i<j时num[i][j] = min(num[i][k]+num[k+1][j]+m[i-1]*m[k]*m[j])(k为[i,j)间的值,即表示以第i个矩阵到j-1个矩阵之间第k个矩阵作为分割处)
解释:i=j自然不用计算,第二个既是当k作为分割点时总次数等于i到k个矩阵的相乘最小次数+第k+1个矩阵到第j个矩阵的相乘最小次数+合并时的产生的新次数(最左行数*分割点列数*最右行数)
然后便是正常的翻译了。
其中对于分割方案的输出,只需按照矩阵范围依据cut数组获取切割点输出即可,具体可参考代码。
坑点:
循环范围的设定,注意按是否能到达设定范围
完整代码:
#include<iostream>
using namespace std;
int length;
int cut[][];
int count;
void printCut(int i,int j)
{
if(i == j)
cout << 'A' << i;
else
{
cout << '(';
printCut(i,cut[i][j]);
printCut(cut[i][j]+,j);
count++;
cout << ')';
}
}
int main()
{
ios::sync_with_stdio(false);
int n;
cin >> n;
length = n;
int m[n+];
for(int i = ;i<=n;i++)
cin >> m[i]; int num[n+][n+];//对应矩阵个数即可
for(int i = ;i<=n;i++)
num[i][i] = ; int temp;
count = ;
for(int l = ;l<=n;l++)
//l为1的情况不必讨论,因为就是矩阵自己,所以从2到n依次进行
{
for(int i = ;i<=n-l+;i++)
//从第一个矩阵一直到最后一个可到达的矩阵
//这样i表示的既是连续l个矩阵的第一个矩阵
//根据输入,第i个矩阵的行数:i-1的值,列数:i的值
{
int j = i+l-;//当前l个矩阵的最后一个矩阵下标
num[i][j] = ;//用于比较
for(int k = i;k<=j-;k++)
//k是节点,代表第k和k+1个矩阵之间的那个下标
//所以需要到达j-1,也就是第j个矩阵前的那个位置才算结束
//由位置可知k的值为第k个矩阵的列数即划分开时需要乘的值
{
temp = num[i][k]+num[k+][j]+m[i-]*m[k]*m[j];
//当前划分状态的计算次数,两个num分别为两侧矩阵链的次数,最后一项为合并后的新增次数
if(num[i][j]>temp)
{
num[i][j] = temp;
cut[i][j] = k;
}
}
}
}
printCut(,n);
cout << '\n';
cout << count << '\n';//切割次数
cout << num[][n] << '\n';//计算次数
return ;
}
6.OBST
实现解释:
实际等价于矩阵链乘,状态转移方程都是将左侧和右侧的最优相加后再加上合并新增的次数即可。
新增的次数对矩阵链乘来说就是两个最优部分相乘的次数
对OBST来说则是左右子树本来的搜索代价+左右子树深度加一产生的新代价+此时根节点的搜索代价(其中深度加一的新代价和根节点的搜索代价便可合并为此时所有节点的检索频率之和)
解释:深度加一时左右子树节点的搜索代价都增加了一个结点,因此他们的代价分别增加了一次搜索概率(自己被搜索到的概率),而根节点的搜索代价即本身的搜索概率,相加既是范围内所有结点的搜索概率和。
于是数据记录如下:
e[i][j]存储i到j个结点之间的最小搜索代价
w[i][j]存储i到j个结点的总搜索概率之和(用于状态转移快捷增加搜索概率和)
root[i][j]存储i到j个结点中有最小搜索代价时的根节点的脚标
状态转移方程即:
j = i-1时e[i][j] = 0(不存在根节点,因此也没有搜索代价)
j >= i时e[i][j] = min(e[i][r-1]+e[r+1][j]+w[i][j])(r即某次选择作为根节点的脚标)
对建树方案的实现和矩阵链乘的实现同理,只是由于伪关键字的加入需要进行一些特殊处理,即如果某个方向没有子树则需要手动添加伪关键字d形成的结点,其余子树的输出只需做好根节点和左右的判断即可,具体可参考代码,方案不唯一。
坑点:
注意dp数组的初始化,初始化既是对没检索到情况的初始化(因此添加的值只是伪关键字的搜索概率)
主要难点即理解搜索子树扩增时平均搜索的变化
完整代码:
#include<iostream>
using namespace std;
const int MAX = ;
double **root;
int di;
int n;
void printOBST(int l,int r)
{
if(l == &&r == n)
{
di = ;
cout << "k" << root[][n] << "为根" << '\n';
}
int t,lt,rt;
t = root[l][r];
if(l == t)
cout << "d"<<di++ << "是k"<<t << "的左孩子" << '\n';
else
if(l < t)
{
lt = root[l][t-];
cout << "k"<<lt << "是k"<<t << "的左孩子" << '\n';
printOBST(l,t-);
}
else return ; if(r == t)
cout << "d"<<di++ << "是k"<<t << "的右孩子" << '\n';
else
if(r > t)
{
rt = root[t+][r];
cout << "k"<<rt << "是k"<<t << "的右孩子" << '\n';
printOBST(t+,r);
}
else return ;
}
int main()
{
int maxi;
double temp;
while(cin >> n)
{
double p[n+],q[n+];
for(int i = ;i<=n;i++)
cin >> p[i];
for(int i = ;i<=n;i++)
cin >> q[i];
double e[n+][n+];//需要存储q的内容,因此n+2
double w[n+][n+];//同上
root = new double *[n+];//只是在p中选root因此n+1即可
for(int i = ;i<=n;i++)
root[i] = new double[n+]; for(int i = ;i<=n+;i++)
{
e[i][i-] = q[i-];
w[i][i-] = q[i-];
}
for(int l = ;l<=n;l++)
{
for(int i = ;i<=n-l+;i++)
{
maxi = i+l-;
//i+l-1就是长度l时能到达的最大的数据下标
e[i][maxi] = MAX;
w[i][maxi] = w[i][maxi-]+p[maxi]+q[maxi];
//i--j的总概率和即等于i--j-1的加上新增的maxi处的k和d的概率
for(int j = i;j<=maxi;j++)
{
temp = e[i][j-]+e[j+][maxi]+w[i][maxi];
if(temp < e[i][maxi])
{
e[i][maxi] = temp;
root[i][maxi] = j;
}
}
}
}
cout << "最小搜索期望为:" << e[][n] << '\n';
cout << "树的形状如下:" << '\n';
printOBST(,n);
}
return ;
}
C语言算法动态规划板子题汇总的更多相关文章
- 2021字节跳动校招秋招算法面试真题解题报告--leetcode19 删除链表的倒数第 n 个结点,内含7种语言答案
2021字节跳动校招秋招算法面试真题解题报告--leetcode19 删除链表的倒数第 n 个结点,内含7种语言答案 1.题目描述 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点. ...
- 算法-动态规划 Dynamic Programming--从菜鸟到老鸟
算法-动态规划 Dynamic Programming--从菜鸟到老鸟 版权声明:本文为博主原创文章,转载请标明出处. https://blog.csdn.net/u013309870/ar ...
- 数据结构+算法面试100题~~~摘自CSDN
数据结构+算法面试100题~~~摘自CSDN,作者July 1.把二元查找树转变成排序的双向链表(树) 题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调 ...
- 常见算法是js实现汇总(转载)
常见算法是js实现汇总 /*去重*/ <script> function delRepeat(arr){ var newArray=new Array(); var len=arr.len ...
- nyist oj 311 全然背包 (动态规划经典题)
全然背包 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描写叙述 直接说题意,全然背包定义有N种物品和一个容量为V的背包.每种物品都有无限件可用.第i种物品的体积是c,价值是 ...
- POJ 3278 Catch That Cow(BFS,板子题)
Catch That Cow Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 88732 Accepted: 27795 ...
- hihoCoder #1038 : 01背包(板子题)
#1038 : 01背包 时间限制:20000ms 单点时限:1000ms 内存限制:256MB 描述 且说上一周的故事里,小Hi和小Ho费劲心思终于拿到了茫茫多的奖券!而现在,终于到了小Ho领取奖励 ...
- 《C语言程序设计》编程总结汇总
<C语言程序设计>编程总结汇总 院系: 专业年级: 班级名称: 学号: 姓名: 指导教师: 完成时间: 自我评价: 计算机科学与技术专业教研室 2018 年秋季学期 第四周编程总结 题目4 ...
- 洛谷P1858 多人背包 多人背包板子题/多人背包学习笔记
,,,本来自以为,我dp学得还挺好的 然后今天一考发现都不会啊QAQ 连最基础的知识点都不清楚啊QAQ 所以就来写个题解嘛! 先放下板子题 其实我jio得,这题只要大概了解方法就不是很难鸭,,,毕竟是 ...
随机推荐
- C语言入门-数据类型
一.C语言的类型 整数:char.short.int.long.longlong 浮点型:float.double.long double 逻辑:bool 指针 自定义类型 类型有何不同 类型名称:i ...
- layer.open打开一个新的jsp页面,如何关闭并刷新父页面问题
layer.open打开一个新的jsp页面弹框,如何关闭呢? 在新的页面提交完毕之后,关闭并刷新父页面列表. layer.closeAll(); parent.layer.closeAll(); wi ...
- spring 定时器知识点
一.各域说明 字段域 秒 分 时 日 月 星期(7为周六) 年(可选) 取值范围 0-59 0-59 0-23 1-31 1-12或JAN–DEC 1-7或SUN–SAT 1970–2099 可用字符 ...
- python爬虫遇到会话存储sessionStorage
记录一次爬虫生成链接过程中遇到的sessionStorage存储数据 1.简介 sessionStorage 是HTML5新增的一个会话存储对象,用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标 ...
- Jetpack系列:Paging组件帮你解决分页加载实现的痛苦
相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...
- 集合查询表--Map
查询表 Map接口java提供了一组可以以键值对(key-value)的形式存储数据的数据结构,这种数据结构成为Map.我们可以把Map看成一个多行两列的表格,其中第一列存放key,第二列存放valu ...
- ELK 学习笔记之 Logstash之inputs配置
Logstash之inputs配置: input plugin doc: https://www.elastic.co/guide/en/logstash/current/index.html 插件很 ...
- MongoDB 学习笔记之 GridFS
GridFS: GridFS 是 MongoDB 的一个用来存储/获取大型数据(图像.音频.视频等类型的文件)的规范.它相当于一个存储文件的文件系统,但它的数据存储在 MongoDB 的集合中.Gri ...
- 对接第三方服务引起的小思考-回调和Sign算法
背景 最近在对接一个同事写的支付公用模块,然后对第三方服务引起一两个小思考. 思考 回调 来看看我们同事是如何做回调的. 首先,请求支付接口的时候,将回调URL作为请求body的一个参数[不加密] ...
- EF Core设置字段默认时间
---恢复内容开始--- 在EF的官方文档上只提到了用 Fluent API来设置默认值. 但是我们日常开发中,会把公用字段都写成基类.比如行创建时间 在需要默认时间的字段加上一个特性 [Databa ...