习题整理(动态规划 2022/4/23)

提示:个人思路


题目


一、最大矩形c++题解

题目:给定一个仅包含 0 和 1 、大小为 rows * cols 的二维二进制矩阵 matrix,找出只包含 1 的最大矩形,并返回其面积。

输入输出格式
输入格式
输入一个仅包含 0 和 1 、大小为 rows * cols 的二维二进制矩阵(列表)。
输出格式
输出只包含 1 的最大矩形面积的整数结果。

输入输出样例1
输入

[[“1”,“0”,“1”,“0”,“0”],
[“1”,“0”,“1”,“1”,“1”],
[“1”,“1”,“1”,“1”,“1”],
[“1”,“0”,“0”,“1”,“0”]]

1.思路

  • 这道题可以使用暴力和动态规划,我会简单介绍一下动态规划的解法,以这道题具体为例。来做,接下来我会简单介绍一下动态规划是什么。
  • 动态规划简单来说就是我们利用历史数据去推出新的数据来,减少我们的计算量。
  • 我也不会着重介绍,最优子结构跟重复子问题,我就简单讲一下,怎么解题? 动态规划的题可以分为三部:
  • 第一步我们要确定我们数组里的每个元素代表的是什么含义 第二步,我们要确定初始值该怎么设立 第三步,我们要确定如何用历史数据去推出新的数据
  • 我们以这道题为例来看。
  • 首先,我们要确定我们说的这个数组是什么含义?我这里设定一个二维数组,它的含义主要有两个,dp[i][j]代表在第i行中包含第j个元素的最长的连续’1’的长度。例如[1,1,0,1,1],相应的dp矩阵的值为[1,2,0,1,2]
  • 第二步要设定初始值,但是这道题比较特殊,我根据dp数组的之前的元素和输入的题目给定的那个矩阵初来进行判定的,这个在第三步里,我会一起做到。
  • 最后一步,这里设置状态转移方程。首先我要连初始化一起完成,所以我先判断第i行j列的元素是否等于一,如果等于一的话,我还要判断他是不是第一列?如果是第一列的话,则它的长度就是一,如果不是第一列的话,它的长度就是dp[i][j-1]的长度加上一.反之,如果第i行j列的元素是零的话,这个位置dp[i][j]=0就可以,因为他打断了连续长度
if(a[i][j]==1)  
dp[i][j]= j==0?1:dp[i][j-1]+1;  
else  
dp[i][j]=0;   
  • 接下来我开始介绍我具体的思路,这道题首先他会输入一行字符串,然后我首先判断三个最特殊的情况,分别是空字符串只有一个0的字符串和只有一个1字符串分别输出它们的结果.
  • 如果都不是,则我进行接下来的操作,我首先将这些字符串里面的数字都提取出来,存放到一个数组当中,但是由于题里没有告诉我有几行几列,所以我要手动计算,只要我遇到一个反中括号就记行数加一。证明这一行输入完,当我输入完第一行的时候,通过一个字符要占四个字符位(“字符”,)的特征,我算出了一行有多少个元素
  • 这样我就得到了行数跟列数。但由于字符串最外围还有一个反中括号,所以最后我的行数要减去一。
    下一步开始遍历整个矩阵,遍历每一个元素的同时计算最大面积,最大面积的计算原理是这样的.
    假设dp[0][5]的值为5,只考虑第i行为高,时能组成的最大矩阵面积为5,高为1,底为dp[0][5] 。
  • 再考虑下一行,此情况下能组成的最大矩形的高为2,底为min(dp[0][5],dp[0][4])
  • 就这样把第一到第i行,再从第二行第i行.第三到第i行如此全部便利完
  • 紧接着再去便利第二列,如此把所有的列也全都便利完。
  • 通过上面的计算方式,考虑了所有的能够组成的矩形,能够找出最大矩形面积,其实也算是暴力法,不过利用了dp数组保存下来的信息可以减少很多冗余计算。

2.代码


```cpp
#include<bits/stdc++.h>    
using namespace std;      
int main(){    
   string s;    
   int k=0,max=0,min=0,ls=100000,hs=0;    
   int a[100][100],dp[100][100];    
       getline(cin,s);//输入一行字符串  
       if(s=="[]"||s=="[0]")//判断最特殊的三种情况  
       cout<<0;  
       else if(s=="[1]")  
       cout<<1;   
       else{    
       for(int i=0;i<s.length();i++){//退出条件为将整个字符串全部分割完成    
         if(s[i]==']')//判断一共有多少行  
          hs++;  
         if(s[i]==']'&&hs==1)//判断一行有多少个元素,即有多少列  
              ls=(i-1)/4;  
         if(s[i]=='1')  
           a[hs][k++]=1;//将字符串类型转化为整型    
        if(s[i]=='0')  
        a[hs][k++]=0;  
        k=k%ls;//类似循环数组,控制列数  
       }   
       hs--;        
    for(int i=0;i<hs;i++)  
       for(int j=0;j<ls;j++)//初始化整个dp数组的值  
        {if(a[i][j]==1)  
          dp[i][j]= j==0?1:dp[i][j-1]+1;  
        else  
           dp[i][j]=0;}  
    for(int j=ls-1;j>=0;j--)//从最后一列到第零列,遍历所有的列数  
     for(int i=0;i<hs;i++)//这两个循环是控制第一行到第四行,第二行到第四行,如此一直便利下去  
       {  min=dp[i][j];  
          for(int z=i;z<hs;z++)  
       {  
          if(dp[z][j]==0)//如果有一个数等于零了,它的最小值已经为零,所以矩形的面积为零,直接跳过本次循环  
            continue;  
          if(min>dp[z][j])//选出已经计算的该列的行数内最小的那个数  
             min=dp[z][j];  
          if(min*(z-i+1)>max)//计算最大矩形面积  
          max=min*(z-i+1);  
       }}  
       cout<<max;  
       }  
    return 0;    
}   

二、莫比乌斯反演

题目:小明正在参加一次数学考试,现在他遇到了这样一道难题:给定 5 个整数:a,b,c,d,k,你需要在 a 到 b 之间找到一个整数 x,在 c 到 d 之间找到一个整数 y,使得 gcd(x,y)=k。gcd(x,y) 表示 x 和 y 的最大公约数。由于选择的数量可能非常大,您只需要输出不同数对的总数。
(x=5,y=7) 和 (x=7,y=5) 被认为是相同的。
假设 a=c=1 始终成立。

输入输出格式:
输入格式
输入包含五个整数 a,b,c,d,k,含义如题目描述所示。整数之间以空格间隔。
输出格式
针对输入,打印出不同数对的总数。

输入输出样例1
输入
1 3 1 5 1
输出
9

1.思路

  • 这道题思路本身并不是很难,最简单的思路,其实就是暴力便利,定义i,从一开始,小于等于b,定义j也是从一开始小于等于d(因为题里说了a=c=1
    始终成立)。
  • 如果我们直接计算两个数的最大公约数,去跟k做比较的话,他的时间会超时,所以我们要通过其他的办法,优化我们的算法.
  • 在这里我们要用到求最小公倍数的一个数学公式就是(gcd(a,b)lcm(a,b)=ab)(两个数最小公倍数乘以两个数最大公约数等于两个数相乘),所以由上面数约公式我们可以得到lcm(a,b)=a*b/gcd(a,b)(两个数最小公倍数等于两个数乘积除以两个数最大公约数)。
  • 在循环中我加了三重判断。我们先假设i和j的最大公约数就是k,我们做除法得到最小公倍数,然后分别对i和j取余,判断是否能整除它们。
  • 如果不能整除他们,我们就知道这样计算的结果不是最小公倍数,从而推理得k不是这个i和j的最大公约数。
    如果整除,我们还要考虑最小公倍数一定大于或等于i和j中最大的那个数。
  • 这条也满足的情况下,只能说明这样得出的结果,刚好可以整除i和j,但并不能说明他一定就是最小公倍数,所以我们在使用欧基里德算法,计算i和j的最大公约数,判断是否等于k,如果三个条件全部满足,我们让计数变量加一,最后输出这个计数变量即可

2.代码

#include<bits/stdc++.h>  
using namespace std;  
int gcd(int a,int b){//欧几里德算法计算两个数的最大公约数  
     if(b==0)  
     return a;  
     return gcd(b,a%b);       
}  
int main(){  
    int a,b,c,d,k;  
    int sum=0;  
    cin>>a>>b>>c>>d>>k;  
    for(int i=1;i<=b;i++)//考虑两个范围内,所有的组合情况  
      for(int j=i;j<=d;j++)  
        {  
            if( ((i*j)/k)%i==0 && ((i*j)/k)%j==0)//先假设i和j的最大公约数就是k,我们做除法得到最小公倍数,然后分别对i和j取余,判断是否能整除它们  
               if( ((i*j)/k)>=i && ((i*j)/k)>=j )//最小公倍数一定大于或等于`i`和`j`中最大的那个数  
                  if(gcd(i,j)==k)//计算`i`和`j`的最大公约数,判断是否等于`k`  
                     sum++;  
        }  
    cout<<sum;  
    return 0;  
}   

三、昆虫繁殖

题目:XXXXX 科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强。每对成虫过 x 个月产 y 对卵,每对卵要过两个月长成成虫。假设每个成虫不死,第一个月只有一对成虫,且卵长成成虫后的第一个月不产卵(过 X 个月产卵),问过 Z 个月以后,共有成虫多少对?

输入输出格式
输入格式
x ,y , z 的数值。
输出格式
过 Z 个月以后,共有成虫对数。

输入输出样例1
输入
1 2 8
输出
37

1.思路

  • 这道题其实就是斐波那契数列的扩展。
    菲波那契数列源自于一个问题,就是一个月有一对刚出生的兔子,第二个月进入成熟期,第三个月开始生,一对兔子会生一对兔子,兔子永不死去,n个月以后有多少兔?
  • 大部分的同学肯定对这个问题很耳熟,他就是从第三项开始数列里的,每一项等于前两项之和他其实运用的就是动态规划的思想,接下来我会简单介绍一下动态规划是什么。
  • 动态规划简单来说就是我们利用历史数据去推出新的数据来,减少我们的计算量。
    我也不会着重介绍,最优子结构跟重复子问题,我就简单讲一下,怎么解题?
  • 动态规划的题可以分为三部:
  • 第一步我们要确定我们数组里的每个元素代表的是什么含义
  • 第二步,我们要确定初始值该怎么设立?
  • 第三步,我们要确定如何用历史数据去推出新的数据
    我们以这道题为例来看。首先,我们要确定我们说的这个数组是什么含义?我这里的表是b[i]代表的,就是第i个月有多少对成虫。第一步就完成了
  • 第二步要设定初始值,根据题意,我们可以知道,在前x月成虫的数量都是一对儿,然后因为过了x月生的卵,还要两个月才能成熟。所以,前x+2个月都是一对成虫,设b[1]~b[x+2]全为一。
  • 最后一步也是最麻烦的一步,我们要找到历史数据和新数据之间的关系,通过题意,我们可以得知我第i月的成虫数量是b[i-1]月成虫的数量,加上x-2个月之前的成虫数量乘以y,因为我这个月的虫虫其实是我前x-2(X月产完卵以后还要两个月成熟,一次产y对)月产下来的卵。
  • 所以我们就推出了公式b[i]=b[i-1]+y*b[i-2-x],但还要注意一点,题里明确的说是过完第一个月,所以我们的循环条件判断内里要往上加一,最后,输出结果即可

2.代码

a = input()  
a = a.split() #以空格作为分隔符将字符串分割成一个列表  
a = [int(i) for i in a]  #这一部是将a列表中的所有元素转化为,整数类型,然后形成一个新的列表返回
b = [0] #第0个月有0对成虫  
for i in range(1, a[0] + 3): #设`b[1]~b[x+2]`全为一  
    b.append(1)  
for i in range(a[0]+3,a[2]+2): #第`i`月的成虫数量是i-1月成虫的数量,加上x-2个月之前的成虫数量乘以`y`  
    b.append(b[i-1]+a[1]*b[i-2-a[0]])  
print(b[-1]) #最后一个月的成虫数  
  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歆雫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值