<算法学习>动态规划练习题

这篇具有很好参考价值的文章主要介绍了<算法学习>动态规划练习题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇文章为初学动态规划时的练习题。参考优质博客学习后根据伪代码描述完成代码。记录一下用于以后复习。

一、数字三角形问题

问题描述

给定一个有n行数字组成的数字三角形. 试设计一个算法, 计算出从三角形的顶至底的一条路径, 使该路径经过的数字和最大.

算法设计: 对于给定的n行数字组成的三角形, 计算从三角形顶至底的路径经过的数字和的最大值.

数据输入: 第1行数字三角形的行数n, 1<=n<=100. 接下来n行是数字三角形各行中的数字. 所有数字在0~99之间.

结果输出: 第1行中的数是计算出的最大值.

测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1 以文本方式显示
  1. 5↵
  2. 7↵
  3. 3 8↵
  4. 8 1 0 ↵
  5. 2 7 4 4↵
  6. 4 5 2 6 5↵
以文本方式显示
  1. 30↵
1秒 64M 0

 问题分析

学习这篇文章     入门DP | 1:数字三角形问题

代码练习

#include <iostream>
#include <algorithm>
#define N 1000
using namespace std;
int a[N][N];
int dp[N][N];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            cin>>a[i][j];
            getchar();
        }
    }
    for(int i=n-1;i>=0;i--)   //自底向上计算
    {
        for(int j=0;j<=i;j++)
        {
            if(i==n-1)   //最后一层直接加
            {
                dp[i][j]=a[i][j];
            }
            else   //上层要考虑本身值加上下层左右孩子中最大的
            {
                dp[i][j]=a[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
            }
        }
    }
    cout<<dp[0][0]<<endl;
    return 0;
}

二、矩阵链乘问题

问题描述

输入:

共两行

第一行 N ( 1<=N<=100 ),代表矩阵个数。

第二行有 N+1 个数,分别为 A1 、 A2 ...... An+1 ( 1<=Ak<=2000 ), Ak 和 Ak+1 代表第 k 个矩阵是个 Ak X Ak+1 形的。

输出:

共两行

第一行 M ,为最优代价。注:测试用例中 M 值保证小于 2^31

第二行为最优顺序。如 (A1((A2A3)A4)) ,最外层也加括号。

注意:测试用例已经保证了输出结果唯一,所以没有AAA的情况.

测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1 以文本方式显示
  1. 6↵
  2. 30 35 15 5 10 20 25↵
以文本方式显示
  1. 15125↵
  2. ((A1(A2A3))((A4A5)A6))↵
1秒 64M 0

算法分析 

 学习这篇文章  区间DP | 1:矩阵链乘问题(含优化) —— 例题:矩阵链乘、合并石子

代码练习

#include <iostream>
#include <limits.h>
using namespace std;
#define N 110
int dp[N][N];
int part[N][N];
int a[N];
void Matrix_multiply(int n)
{
    for(int l=2;l<=n;l++)  //l表示从2链矩阵开始求最优值,一直讨论到n链矩阵
    {
        for(int i=1;i<=n-(l-1);i++)  //i是从1到n-(l-1)的,因为2链矩阵组数会吞掉一个矩阵,3链会吞掉2个,以此类推
        {
            int j=i+l-1;   //j表示该组矩阵链中最后一个矩阵的列数下标
            dp[i][j]=INT_MAX;  //初始全设为无穷大
            for(int k=i;k<j;k++)  //在i到j的范围内找最优划分
            {
                int t=dp[i][k]+dp[k+1][j]+a[i-1]*a[k]*a[j];  //注意第i个矩阵的行列大小是a[i-1]*a[i]
                if(t<dp[i][j])  //如果找到优于最优划分情况的,就更新最优乘法次数和最优划分位置
                {
                    dp[i][j]=t;
                    part[i][j]=k;
                }
            }
        }
    }
}
void PrintPart(int i,int j)  //输出划分结果
{
    if(i==j) cout<<"A"<<i;  //i=j表示此时只有一个矩阵相乘,直接输出
    else    
    {
        int k=part[i][j];   //否则需要递归输出划分结果
        cout<<"(";
        PrintPart(i,k);
        PrintPart(k+1,j);
        cout<<")";
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<=n;i++)
    {
        cin>>a[i];
    }
    Matrix_multiply(n);
    cout<<dp[1][n]<<endl;  //不管几个矩阵都要先输出最少乘法次数
    if(n==1)   //单独讨论只有一个矩阵的情况,别忘了加()
    {
        cout<<"(A1)"<<endl;
        return 0;
    }
    PrintPart(1,n);
    cout<<endl;
    return 0;
}

三、石子合并问题

问题描述

在一个圆形操场的四周摆放着n堆石子. 现在要将石子有次序地合并成一堆. 规定每次只能选相邻的2堆石子合并成一堆, 并将新的一堆石子数记为该次合并的得分. 试设计一个算法, 计算出将n堆石子合并成一堆的最小得分和最大得分.

算法设计: 对于给定n堆石子, 计算合并成一堆的最小得分和最大得分.

数据输入: 第1行是正整数n, 1<=n<=100, 表示有n堆石子. 第2行有n个数, 分别表示n堆石子的个数.

结果输出: 第1行是最小得分, 第2行是最大得分.

测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1 以文本方式显示
  1. 36↵
  2. 53 49 2 9 9 30 2 35 1 46 39 46 42 33 13 41 35 57 38 59 15 40 18 6 46 30 53 31 34 57 41 20 1 42 59 46 ↵
以文本方式显示
  1. 5913↵
  2. 24595↵
1秒 64M 0

算法分析

如果是线性石子堆,其实跟矩阵链乘是非常类似的。

矩阵链乘是相邻的两个可乘,需要由底向上乘起来,先乘2个的,再乘3个的,直到n个。

石子合并是相邻的两堆可加,需要由底向上加起来,先加两堆,再加三堆,直到n堆。

所以对于线性石子合并问题来说,与矩阵链乘的代码并无太大差异。唯一不同的是更新dp数组的值时,除了划分的两块加数外,新加的当前这一项不再是a[i-1]*a[k]*a[j],而是从 i 到 j 的石子堆中石子总数。(因为每次合并都要把当前合并的石子数再加一遍)(其实这个运算跟矩阵链乘也有异曲同工之处)

代码如下

#include <iostream>
#include <algorithm>
#include <limits.h>
using namespace std;
#define N 110
int dp_min[N][N];
int dp_max[N][N];
int a[N];
void Stones(int n)
{
    for(int l=1;l<=n;l++)  //与矩阵链乘相似,由底向上,此循环代表几堆石子合并(从1堆开始)
    {
        for(int i=1;i<=n-(l-1);i++)  
        {
            int j=i+l-1,sum=0;
            dp_min[i][j]=INT_MAX; //初始化dp
            dp_max[i][j]=INT_MIN;
            if(i==j)  //一堆石子不合并
            {
                dp_min[i][j]=0;
                dp_max[i][j]=0;
                continue;
            }
            for(int m=i;m<=j;m++)  //计算从当前一次合并需要加的数值(从第i到j堆的石子数之和)
            {
                sum+=a[m];
            }
            for(int k=i;k<j;k++) //更新dp
            {
                int t1=dp_min[i][k]+dp_min[k+1][j]+sum; //试图找更优的划分
                int t2=dp_max[i][k]+dp_max[k+1][j]+sum;
                if(t1<dp_min[i][j])  //尝试更新最小值和最大值
                {
                    dp_min[i][j]=t1;
                }
                if(t2>dp_max[i][j])
                {
                    dp_max[i][j]=t2;
                }
            }
        }
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    Stones(n);
    int resmin=dp_min[1][n];  
    int resmax=dp_max[1][n]; 
    cout<<resmin<<endl;
    cout<<resmax<<endl;
    return 0;
}

下面我们讨论一下如果是环形石子堆应该怎么办。

我们可以把环形石子断开,将其展成一条链,这样就跟线性石子合并求法相同了。只是对于从哪里断开,我们有多种选择。比如4堆石子:1234这个序列,我们可以展成1234,2341,3412,4123四种链条。对于每一条链,我们都需要求出其线性石子合并的最小值(和最大值),最后再对这四条链作比较选出最小的最小值(和最大的最大值)

所以我们可以把链条长度延长到2n,0处空着,从1开始放第一堆石子。还是以1234为例,延展后长为2n的石子链条数组变为  空1234123  ,则第一种链是1234,往后移一位得到第二种链2341,第三种和第四种链依次再往后移一位。所以这条延长链构成的dp数组中会保存四种链的石子合并值,最终四种链条的石子合并值会分别保留在dp[1][4],dp[2][5],dp[3][6],dp[4][7]中。

由此,我们可以写出如下代码

代码练习

#include <iostream>
#include <algorithm>
#include <limits.h>
using namespace std;
#define N 110
int dp_min[2*N][2*N];
int dp_max[2*N][2*N];
int a[2*N];
void Stones(int n)
{
    for(int l=1;l<=n;l++)  //与矩阵链乘相似,由底向上,此循环代表几堆石子合并(从1堆开始)
    {
        for(int i=1;i<=2*n-(l-1);i++)  //对于每一条链,延展开变成2n长度
        {
            int j=i+l-1,sum=0;
            dp_min[i][j]=INT_MAX; //初始化dp
            dp_max[i][j]=INT_MIN;
            if(i==j)  //一堆石子不合并
            {
                dp_min[i][j]=0;
                dp_max[i][j]=0;
                continue;
            }
            for(int m=i;m<=j;m++)  //计算从当前一次合并需要加的数值(从第i到j堆的石子数之和)
            {
                sum+=a[m];
            }
            for(int k=i;k<j;k++) //更新dp
            {
                int t1=dp_min[i][k]+dp_min[k+1][j]+sum; //试图找更优的划分
                int t2=dp_max[i][k]+dp_max[k+1][j]+sum;
                if(t1<dp_min[i][j])  //尝试更新最小值和最大值
                {
                    dp_min[i][j]=t1;
                }
                if(t2>dp_max[i][j])
                {
                    dp_max[i][j]=t2;
                }
            }
        }
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        a[i+n]=a[i];  //因链条延展了,所以输入数据时要往后copy一份
    }
    Stones(n);
    int resmin=INT_MAX;  //在n种链条中找合并石子所得最小值中最小的那一种
    for(int i=1;i<=n;i++)
    {
        if(resmin>dp_min[i][i+n-1])  //注意j的下标是i+n-1,用1234为例写一下看看,如第一种链的 
                                                                   //结果在dp_min[1][4]中
        {
            resmin=dp_min[i][i+n-1];
        }
    }
    int resmax=INT_MIN;  //在n种链条中找合并石子所得最大值中最大的那一种
    for(int i=1;i<=n;i++)
    {
        if(resmax<dp_max[i][i+n-1])
        {
            resmax=dp_max[i][i+n-1];
        }
    }
    cout<<resmin<<endl;
    cout<<resmax<<endl;
    return 0;
}

四、租用游艇问题

问题描述

问题描述: 长江游艇俱乐部在长江上设置了n个游艇出租站1,2,…,n. 游客可在这些游艇出租站租用游艇, 并在下游的任何一个游艇出租站归还游艇. 游艇出租站i到出租站j之间的租金为r(i,j), 1<=i<j<=n. 试设计一个算法, 计算出从游艇出租站1到游艇出租站n所需的最少租金, 并分析算法的计算复杂性.

算法设计: 对于给定的游艇出租站i到游艇出租站j的租金r(i,j), 1<=i<j<=n. 计算出租站1到n所需的最少租金.

数据输入: 第1行有一个正整数n, n<=200, 表示有n个游艇出租站. 接下来n-1行是r(i,j), 1<=i<j<=n.

结果输出: 游艇出租站1到n最少租金.

测试输入 期待的输出 时间限制 内存限制 额外进程
测试用例 1 以文本方式显示
  1. 3↵
  2. 5 15↵
  3. 7↵
以文本方式显示
  1. 12↵
1秒 64M 0

算法分析

这道题跟矩阵链乘和石子合并其实有点像。

题意不太好懂,解释一下。输入的是第行号个出租站到第列号+1个出租站的费用。比如测试用例1,第一行3代表有三个出租站,第二行5 15分别为第一个出租站到第二个出租站的费用和第一个出租站到第三个出租站的费用。第三行7代表第二个出租站到第三个出租站的费用。

我们可以看出输入的数据其实是一个二维的上三角矩阵(也可以理解为无向完全图,即每两个点之间有一条边,每条边只保存一次)。

<算法学习>动态规划练习题,算法学习,算法,学习,动态规划

所以我们可以把原始数据保存在一个二维数组中,按照上述形式存储它。

dp[][]数组由此也设为二维数组。dp[i][j]表示从第i个出租站到第j个出租站所需的最小费用。

状态转移方程为dp[i][j]=min(dp[i][k]+dp[k][j],dp[i][j])

跟矩阵链乘和石子合并问题不同的是,如果需要更新dp的值,只需加划分后的两部分即可,无需额外加本次合并总体所需的什么数值。

而且划分后的两部分是dp[i][k]+dp[k][j],而不是dp[i][k]+dp[k+1][j]。因为从第i个出租站到第i个出租站本身不需要单独费用,但石子合并和矩阵链乘中每一项都有一个数值需要考虑加和。文章来源地址https://www.toymoban.com/news/detail-796413.html

代码练习

#include <iostream>
#include <algorithm>
#include <limits.h>
using namespace std;
#define N 220
int dp[N][N];
int a[N][N];
void Rent_Yacht(int n)
{
    for(int l=2;l<=n;l++) //类似合并石子,从两个出租站开始合并
    {
        for(int i=1;i<=n-(l-1);i++)
        {
            int j=i+l-1;
            dp[i][j]=a[i][j];
            for(int k=i;k<j;k++)  //试图寻找更优划分
            {
                int temp=dp[i][k]+dp[k][j]; //计算划分后的费用值
                if(dp[i][j]>temp && temp!=0) dp[i][j]=temp; //尝试更新
            }
        }
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            cin>>a[i][j];
            getchar();
        }
    }
    Rent_Yacht(n);
    cout<<dp[1][n]<<endl;
    return 0;
}

到了这里,关于<算法学习>动态规划练习题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 拓扑排序 (算法思想+图解+模板+练习题)

    有向无环图一定是拓扑序列,有向有环图一定不是拓扑序列。 无向图没有拓扑序列。 首先我们先来解释一下什么是有向无环图: 有向就是我们两个结点之间的边是有方向的,无环的意思就是整个序列中没有几个结点通过边形成一个圆环。 下图就是一个有向无环图,它也一定

    2024年02月03日
    浏览(36)
  • 数据结构与算法系列之习题练习

    💗 💗 博客:小怡同学 💗 💗 个人简介:编程小萌新 💗 💗 如果博客对大家有用的话,请点赞关注再收藏 🌞 括号匹配问题。 用队列实现栈。 用栈实现队列。 设计循环队列。 有效的括号 //用栈来实现 //左括号进栈 右括号出栈并销毁如果不匹配则return //设置两个队列,入栈

    2024年02月11日
    浏览(29)
  • 数据结构与算法--图(概念+练习题+解析)

    有向图 在有向图中有以下几点结论: 1.所有顶点的度数之和等于边数的二倍。 2.所有顶点的入度之和等于出度之和。 3.n个顶点的有向完全图有n(n-1)条边。 4.n个顶点的强连通图至少有n条边。 无向图 在无向图中有以下几点结论: 1.所有顶点的度数之和等于边数的二倍。 2.n个顶

    2024年02月04日
    浏览(32)
  • 数学建模算法与应用:预测算法(6)预测习题练习

    目录  一,水塔总水量以及流速预测问题         1.1、题目         1.2、建立模型         1.3、用MATLAB计算,将“-”替换为-1。         1.4、拟合法          二、预测产值问题         2.1、题目         2.2、建立模型  一,水塔总水量以及流速预测问题        

    2024年02月13日
    浏览(31)
  • 每天一道算法练习题--Day18 && 第一章 --算法专题 --- ----------前缀树

    字典树也叫前缀树、Trie。它本身就是一个树型结构,也就是一颗多叉树,学过树的朋友应该非常容易理解,它的核心操作是插入,查找。删除很少使用,因此这个讲义不包含删除操作。 截止目前(2020-02-04) 前缀树(字典树) 在 LeetCode 一共有 17 道题目。其中 2 道简单,8 个

    2024年02月02日
    浏览(37)
  • 每天一道算法练习题--Day22&& 第一章 --算法专题 --- ----------最大公约数

    关于最大公约数有专门的研究。 而在 LeetCode 中虽然没有直接让你求解最大公约数的题目。但是却有一些间接需要你求解最大公约数的题目。 时间复杂度:最好的情况是执行一次循环体,最坏的情况是循环到 smaller 为 1,因此总的时间复杂度为 O ( N ) O(N) O ( N ) ,其中 N 为 a 和

    2024年02月03日
    浏览(42)
  • 算法(第4版)练习题1.1.27的三种解法

    本文列举了对于 算法 : 第4版 / (美) 塞奇威客 (Sedgewick, R.) , (美) 韦恩 (Wayne, K.) 著 ; 谢路云译. -- 北京 : 人民邮电出版社, 2012.10 (2021.5重印) (以下简称原书或书)中的练习题 1.1.27 的三种解法(C++ 实现),并对包含原书题中的递归方法在内的四种解法的执行时间进行了统计和对

    2023年04月19日
    浏览(24)
  • 每天一道算法练习题--Day15 && 第一章 --算法专题 --- -----------二叉树的遍历

    二叉树作为一个基础的数据结构,遍历算法作为一个基础的算法,两者结合当然是经典的组合了。很多题目都会有 ta 的身影,有直接问二叉树的遍历的,有间接问的。比如要你找到树中满足条件的节点,就是间接考察树的遍历,因为你要找到树中满足条件的点,就需要进行遍

    2024年02月01日
    浏览(35)
  • luatOS网站 lua语言学习 练习题

    lua 教程跳转链接,练习题都来自这里 题目:如果已知number变量n,那么如果需要判断n是否符合下面的条件: 3n≤10 以下四行判断代码,正确的是? (返回true即表示变量n符合要求) 你需要使用前面几章的知识,来完成下面的题目 已知三个number类型的变量a、b、c,分别代表三

    2024年02月06日
    浏览(31)
  • 机器学习课后练习题(期末复习题目附答案)

    此为第一章绪论部分 一. 单选题 1. 移动运营商对客户的流失进行预测,可以使用下面哪种机器学习方法比较合适( ) A. 一元线性回归分析 B. 关联方法 C. 聚类算法 D. 多层前馈网络 正确答案: A 2. 下面哪种说法有关机器学习的认识是错误的?( ) A. 高质量的数据、算力和算法对一个机

    2024年02月07日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包