【考题详解】 4月DP练习赛题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/81873625

跑步[Neal Wu, 2007]

奶牛们打算通过锻炼来培养自己的运动细胞,作为其中的一员,贝茜选择的运动方式是每天进行N(1 <= N <= 10,000)分钟的晨跑。在每分钟的开始,贝茜会选择下一分钟是用来跑步还是休息。贝茜的体力限制了她跑步的距离。更具体地,如果贝茜选择在第i分钟内跑步,她可以在这一分钟内跑D_i(1 <= D_i <= 1,000)米,并且她的疲劳度会增加1。不过,无论何时贝茜的疲劳度都不能超过M(1 <= M <= 500)。如果贝茜选择休息,那么她的疲劳度就会每分钟减少1,但她必须休息到疲劳度恢复到0为止。在疲劳度为0时休息的话,疲劳度不会再变动。晨跑开始时,贝茜的疲劳度为0。还有,在N分钟的锻炼结束时,贝茜的疲劳度也必须恢复到0,否则她将没有足够的精力来对付这一整天中剩下的事情。
请你计算一下,贝茜最多能跑多少米。
程序名: cowrun
【输入格式】
第1行: 2个用空格隔开的整数:N 和 M
第2..N+1行: 第i+1为1个整数:D_i
【输入样例】 (cowrun.in)
5 2
5
3
4
2
10
【输出格式】
第1行: 输出1个整数,表示在满足所有限制条件的情况下,贝茜能跑的最大距离
【输出样例】 (cowrun.out)
9
【输出说明】
贝茜在第1分钟内选择跑步(跑了5米),在第2分钟内休息,在第3分钟内跑步(跑了4米),剩余的时间都用来休息。因为在晨跑结束时贝茜的疲劳度必须为0,所以她不能在第5分钟内选择跑步。

这是一道动态规划的题目,有两个状态:时间和疲劳值。因此我们就可以设F[i][j]为前i分钟疲劳值为j的最大跑步距离。但是这道题目有一点特殊,因为有3种情况。
1.若当前休息且疲劳值为0,有可能是由上一秒疲劳值为0转移过来的。
2.若当前跑完步后准备休息,那么直接休息到底,即直接转移到休息完的状态
3.这一秒跑步且上一秒也跑步,直接转移

基于上述三种情况,我们可以得到3个状态转移方程
1. F [ i ] [ 0 ] = m a x ( F [ i ] [ 0 ] , F [ i 1 ] [ 0 ] )
2. F [ i + j ] [ 0 ] = m a x ( F [ i + j ] [ 0 ] , F [ i ] [ j ] ) ,表示由当前状态直接休息到底
3. F [ i ] [ j ] = m a x ( F [ i ] [ j ] , F [ i 1 ] [ j 1 ] + a [ i ] ) ,其中a[i]表示跑步的时间,需要注意的是我们必须判断上一个位置知否合法。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int a[1000000];
int F[20000][1500];
inline int Max(int a,int b,int c){return max(a,max(b,c));}
int main()
{
    freopen("cowrun.in","r",stdin);
    freopen("cowrun.out","w",stdout);
    int n,m;
    cin>>n>>m;
    for (int i=1;i<=n;++i) cin>>a[i];
    for (int i=1;i<=n;++i)
    {
        F[i][0]=max(F[i-1][0],F[i][0]);//判断上一部也是休息的情况 
        for (int j=1;j<=m;++j)
        {
            if (F[i-1][j-1]>0 || j==1) F[i][j]=max(F[i-1][j-1]+a[i],F[i][j]);//判断上一步是运动的状况 
            F[i+j][0]=max(F[i+j][0],F[i][j]);//直接修休息到下一步 
        }
    }
    cout<<F[n][0]<<endl;
    fclose(stdin);fclose(stdout);
    return 0;
}

渡河问题[Jeffrey Wang, 2007]

Farmer John以及他的N(1 <= N <= 2,500)头奶牛打算过一条河,但他们所有的渡河工具,仅仅是一个木筏。
由于奶牛不会划船,在整个渡河过程中,FJ必须始终在木筏上。在这个基础上,木筏上的奶牛数目每增加1,FJ把木筏划到对岸就得花更多的时间。
当FJ一个人坐在木筏上,他把木筏划到对岸需要M(1 <= M <= 1000)分钟。当木筏搭载的奶牛数目从i-1增加到i时,FJ得多花M_i(1 <= M_i <= 1000)分钟才能把木筏划过河(也就是说,船上有1头奶牛时,FJ得花M+M_1分钟渡河;船上有2头奶牛时,时间就变成M+M_1+M_2分钟。后面的依此类推)。那么,FJ最少要花多少时间,才能把所有奶牛带到对岸呢?当然,这个时间得包括FJ一个人把木筏从对岸划回来接下一批的奶牛的时间。
程序名: river
【输入格式】
第1行: 2个用空格隔开的整数:N 和 M
第2..N+1行: 第i+1为1个整数:M_i
【输入样例】 (river.in)
5 10
3
4
6
100
1
【输入说明】
FJ带了5头奶牛出门。如果是单独把木筏划过河,FJ需要花10分钟,带上1头奶牛的话,是13分钟,2头奶牛是17分钟,3头是23分钟,4头是123分钟,将5头一次性载过去,花费的时间是124分钟。
【输出格式】
第1行: 输出1个整数,为FJ把所有奶牛都载过河所需的最少时间
【输出样例】 (river.out)
50
【输出说明】
Farmer John第一次带3头奶牛过河(23分钟),然后一个人划回来(10分钟),最后带剩下的2头奶牛一起过河(17分钟),总共花费的时间是23+10+17 = 50分钟。

这可能是一道最基础的线性且以为的动态规划,类似于公交乘车的思想。即,设F[i]为前i头牛能够度过和的最小话费,那么就枚举j,当前花费为前j头牛的花费加上剩下几头牛的花费。那么就可以写出这道题目的状态转移方程:

F [ i ] = m i n ( F [ i ] , F [ j ] + c o s t [ i j ] )
cost[]类似于一个前缀和,统计的是话费的总和。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int a[1000000];
int F[20000][1500];
inline int Max(int a,int b,int c){return max(a,max(b,c));}
int main()
{
    freopen("cowrun.in","r",stdin);
    freopen("cowrun.out","w",stdout);
    int n,m;
    cin>>n>>m;
    for (int i=1;i<=n;++i) cin>>a[i];
    for (int i=1;i<=n;++i)
    {
        F[i][0]=max(F[i-1][0],F[i][0]);//判断上一部也是休息的情况 
        for (int j=1;j<=m;++j)
        {
            if (F[i-1][j-1]>0 || j==1) F[i][j]=max(F[i-1][j-1]+a[i],F[i][j]);//判断上一步是运动的状况 
            F[i+j][0]=max(F[i+j][0],F[i][j]);//直接修休息到下一步 
        }
    }
    cout<<F[n][0]<<endl;
    fclose(stdin);fclose(stdout);
    return 0;
}

数字平方和

3.数字平方和
(numsquare.pas/c/cpp)
这是一个很无趣的数字游戏,给你n个整数,再给一个目标和sum。你可以调整n个数中任意一个数的大小,但是要让调整好的n个整数的平方和和sum相等。
比如有3个数 1 3 3。目标和是6,那么我们可以把1变成2,把两个3都变成1,这样就完成调整。但是为了提高难度,我们规定假设某个数为A_i,调整为B_i,那么要花费的代价是|A_i-B_i|*|A_i-B_i|。
那么上面的算法花费的代价就是9.当然肯定会有更小代价的算法。
现在问题提出来,把n个数调整到位,最小花费的代价是多少?
如果存在无解的情况,输出”-1”。
【输入】
第一行两个空格隔开的整数:n (1<=n<=10) and sum (1<=sum<=10,000)
接下来为n行,每行一个整数A_i(1<=A_i<=100)
【输出】
一个整数,花费的最小代价

【输入输出样例1】
——————
3 6
3
3
1
——————
5
——————

这道题其实难度较大。我们设F[i][j]为前i和数字累加和为j的最小话费。思维难度较大,但是我们可以去仔细地思考以下:每一个数字a[i],可以改变,同样可以不改变,我们暂且考虑需要改变的情况。需要改变,我们便必然有这么一个改变后的数字,设这个数字我j,改变的花费为cost[i][j],那么我们可以去枚举这个总和,也就是F数组的第二维状态,则可以知道其中必然包含j*j,设k为这个综合减去j*j的数字,则不能得出: F [ i ] [ k + j j ] = ( F [ i ] [ k + j j ] , F [ i 1 ] [ k ] + c o s t [ i ] [ j ] ) 其含义就是原来的数加上改变以后的数的最有值。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,sum;
int a[200000];
int F[20][20000];
int cost[1000][1000];
int main()
{
    freopen("numsquare.in","r",stdin);
    freopen("numsquare.out","w",stdout);
    cin>>n>>sum;
    memset(F,100,sizeof(F));F[0][0]=0;
    for (int i=1;i<=n;i++) cin>>a[i];
    for (int i=1;i<=n;i++)
        for (int j=0;j<=200;j++)
            cost[i][j]=pow( abs(a[i]-j),2 );
    for (int i=1;i<=n;i++)//枚举每一个数字
        for (int j=0;j*j<=sum;j++)//枚举每一个小于sum且需要与a[i]替换的完全平方数 
            for (int k=sum-j*j;k>=0;k--)//枚举剩下的数字
                F[i][k+j*j]=min(F[i][k+j*j],F[i-1][k]+cost[i][j]);
    cout<<(F[n][sum]!=F[15][0]?F[n][sum]:-1);
    fclose(stdin);fclose(stdout);
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/81873625