动态规划习题专练

动态规划习题专练

一、跑步

   题目简述:贝茜每天进行N分钟的晨跑。在每分钟的开始,贝茜会选择下一分钟是用来跑步还是休息。贝茜的体力限制了她跑步的距离。更具体地,如果贝茜选择在第i分钟内跑步,她可以在这一分钟内跑a_i米,并且她的疲劳度会增加1。不过,无论何时贝茜的疲劳度都不能超过M。如果贝茜选择休息,那么她的疲劳度就会每分钟减少1,但她必须休息到疲劳度恢复到0为止。晨跑开始时,贝茜的疲劳度为0。,在N分钟的锻炼结束时,贝茜的疲劳度也必须恢复到0,否则她将没有足够的精力来对付这一整天中剩下的事情。请你计算一下她最多能走多少分钟?

   题意分析:首先,我们要设置一个数组f[i][j]表示在前i分钟内疲劳度为j能走过的最大路程。那么,我们接下来思考如何将状态进行转移:那么我们就从简单的开始想起,当所消耗疲劳度j>0是,我们可以得到一下转移方程:f[i][j]=f[i-1][j-1]+a[i];意思是关于f[i][j](第i分钟消耗j体力的最大距离)的值和f[i-1][j-1]有关。因为f[i-1][j-1]表示第i-1分钟耗费j-1个疲劳度所得到的最大距离,那么既然和f[i][j]有关,那么必然又有一个值回事f[i][j]与f[i-1][j-1]之间的过渡,这个值就是a[i],在第i钟走a[i]个距离,既可以将f[i-1][j-1]所缺的1个时间补回,又可以将1个疲劳度补回,有了这样的支持,f[i][j]=f[i-1][j-1]+a[i]这个转移方程就成立了;那么,那仅仅知识j>0的情况,并不是题目所求,现在我们需要考虑j=0时的情况:1、最明显的,f[i][0]可直接与f[i-1][0]有关,因为f[i-1][0]可以理解为前i-1分钟内有一段时间在休息(这时必然的),那么我们就可以不顾其他,在前i-1分钟的基础上继续休息,这时不影响最终结果的;2、我们考虑不休息时候的情况:我们设一个内层循环j(0<=j<=M)来表示休息的情况,可得出方程f[i][0]=f[i-j][j];  为什么呢:这个方程的值其实是与f[i-j][j]有关的,指在前i-j分钟内所消耗j疲劳度的最大路程。因为在得到了这个最大路程之后我们仍然可以休息j分钟的时间到i分钟从而又不耗费体力。既满足f[i][0]的要求。

    综上所述,我们可以得到一下转移方程:

 f[i][j]=max(f[i-1][j-1]+a[i],f[i][j]) {j>0}

f[i][0]=max(f[i-j][j],f[i][0]) {f[i][0]=f[i-1][0],j=0}

      又即:

那么具体代码如下:

#include<bits/stdc++.h>
int f[10020][601]={},n,m,a[10020]={},x=0;
using namespace std;
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<=n;i++){
      f[i][0]=f[i-1][0];//设初值
      for (int j=1;j<=min(m,i);j++){//在m和i之间取小值,避免小错误
      	  f[i][0]=max(f[i][0],f[i-j][j]);//第一个转移方程
      	  f[i][j]=max(f[i][j],f[i-1][j-1]+a[i]);//第二个转移方程
	  }
    }
	cout<<f[n][0];//输出为在n分钟内体力消耗0的最大路程
    return 0;
}


 二、渡船问题


    题目简述:大佬以及他的N头奶牛打算过一条河,但他们仅仅只有一个木筏。

 由于奶牛不会划船,在整个渡河过程中,大佬必须始终在木筏上。当然,木筏上的奶牛数目每增加1,大佬把木筏划到对岸就得花更多的时间。当大佬一个人坐在木筏上,他把木筏划到对岸需要M分钟。当木筏搭载的奶牛数目从i-1增加到i时,大佬得多花M_i分钟才能把木筏划过河(也就是说,船上有1头奶牛时,FJ得花M+M_1分钟渡河;船上有2头奶牛时,时间就变成M+M_1+M_2分钟……以此类推)。那么,大佬最少要花多少时间,才能把所有奶牛带到对岸呢?(当然,这个时间得包括FJ一个人把木筏从对岸划回来接下一批的奶牛的时间。)

      题意分析:(其实这题是一个完全背包……但在我手上却毁了……完全做成公交乘车了……)首先,我们设置f[i]表示将i头牛送到岸的最小时间。但是,再看题目时,我们会发现一个问题:在乘船过程中奶牛的时间是非常难处理的,所以,为了方便,我们需要先用前缀和做预处理:a[i]表示第一头牛到第i头牛的所需时间总和。预处理做完,我们就可以设置转移方程了:f[1]的值可以确定,就是a[1]的值。我们先用i(1<=i<=n)枚举奶牛过河的个数,在用j(1<=j<=i)枚举界限(即方案),最后可得出方程f[i]=f[i-j]+a[j],但又因为大佬本身的质量所以在末尾还要在加上m,即得到一下方程:

f[i]=min(f[i-j]+a[j]+m,f[i]);

那么具体代码如下:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m,a[3001]={};
	long long f[3001]={};
	scanf("%d%d",&n,&m);
	int x;
	scanf("%d",&x);
	a[1]=x+m;//第一头牛优先处理
	for (int i=2;i<=n;i++)
	  scanf("%d",&x),a[i]=a[i-1]+x;//每次前缀和累加,但又因为回来接奶牛是还有时间,所以每次需要加上x
	for (int i=1;i<=n;i++){
	  f[i]=a[i];//先设初值为一次性载过去的时间
	  for (int j=1;j<=i;j++)
	    f[i]=min(f[i-j]+a[j]+m,f[i]);//转移方程找最小值
	}
	cout<<f[n];
	return 0;
     }

那么这是今天的两道题,先发了;由于时间原因,还有几道题我做成几篇博客一起发。

猜你喜欢

转载自blog.csdn.net/huang_ke_hai/article/details/79977905