大工软件学院DP入门练习题解

传送门:戳这里

第一题:get the ball

状态很容易能看出来是一个二维的状态,定义dp(i,j)表示第i次传球球在第j个人手里的方案数。因为只能从相邻的人手里拿到,所以状态转移方程也很好确定,dp[i,j] = dp[i-1,j-1] + dp[i-1,j+1]。只需要做好初始化就可以了,唯一需要注意的就是这个题不可以用递归的方法来计算,即使做了记忆化依然会超时,因为过程中递归次数过多。

第二题:旅行花费

这个题也是很简单的一个记忆化搜索的题目,定义状态dp(i)表示行驶i公里的最小化费,转移方程就是dp[i] = min(dp[i-j] + f[j]),其中j是小于等于10的正整数。没什么难度,随便过。

第三题:收拾垃圾

这个题目是之前讲过的0/1背包的弱化问题,只需要考虑体积,不用考虑价值。思路类似0/1背包,定义状态方程dp(i)表示i这个体积能否达成,转移方程就是枚举垃圾的体积v[j],dp[i] = dp[i - v[j]]。

注:本次练习的目的是让大家完成前三题,而后三题是让大家见识更多各种各样的动态规划问题,希望看完后三题题解之后,大家能有这样的感受:“我去,这也是DP?”和“我去,这也能DP?”

第四题:赢下游戏

一个区间动规的基础题,没有接触过区间DP的可能会有点困难。取数的过程有一个典型的特点,就是取走一个数之后这个大区间就被分成了两个小区间,也就是说可以定义状态dp(i,j)表示从第i个数到第j个数能得到的最小分数,所以状态转移方程就是枚举这个区间间断点k,dp[i,j] = min(dp[i,k] + dp[k,j] + a[i] * a[k] * a[j])。这样根据这个递推式子递推一下就可以得到正确答案了。问题的分割、整合转移是区间动态规划的典型特点,而如何看出一个问题是区间动规则需要大家更多的接触区间动规的题目,掌握区间的真正含义,而不是仅仅停留在数学上的区间。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
 
using namespace std;
 
const int maxn = 0x3f3f3f3f;
 
int n,a[101];
int f[101][101];
 
int main() {
    //freopen("in.txt","r",stdin);
    while (cin>>n) {
        for (int i = 1; i<=n; i++) cin>>a[i];
        memset(f,maxn,sizeof(f));
        for (int i = 1; i<=n; i++)
            f[i][i-1] = f[i][i] = f[i][i+1] = 0;
        for (int i = n-2; i>0; i--)
            for (int j = i+2; j<=n; j++)
                for (int k = i+1; k<j; k++)
                    if (f[i][j]>f[i][k]+a[i]*a[k]*a[j]+f[k][j])
                       f[i][j]=f[i][k]+a[i]*a[k]*a[j]+f[k][j];
        cout<<f[1][n]<<endl;
    }
}

第五题:休息时间

看问题,不难看出来是一个一维DP,但是问题就出在了如何转移的问题,不妨各种想法都试试,比如就定义dp(i)表示1-i分钟能有多少休息时间,而不难发现这样的方程是存在后效性的,因为第i分钟的工作状态是影响这个状态往后转移的。所以就换一种思想,定义dp(i)表示i-n分钟能休息的时间,可见这个状态是完全可行的,只需要判断是否有在第i分钟开始的工作即可,对于没有在第i分钟开始的情况,dp[i] = dp[i+1] + 1,而对于有开始的就要枚举在第i分钟开始的工作j,计算dp[i+t[j]]的最大值。当然我还是很仁慈的,数据里边的工作开始时间给出的顺序是按照递增顺序给出的,所以一个完美的O(n+k)的复杂度。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
 
using namespace std;
 
const int maxn = 10000 + 5;
int n,k;
int p[maxn],t[maxn],f[maxn];
 
int main() {
    //freopen("in.txt","r",stdin);
    while (cin>>n>>k) {
        for (int i = 1; i <= k; i++) scanf("%d%d",&p[i],&t[i]);
        int x = k;
        memset(f,0,sizeof(0));
        for (int i = n; i > 0; i--) {
            if (i > p[x]) f[i] = f[i+1] + 1;
            else {
                f[i] = f[i+t[x]];
                x--;
                while (i == p[x]) {
                    f[i] = max(f[i],f[i+t[x]]);
                    x--;
                }
            }
        }
        cout<<f[1]<<endl;
    }
}
第六题:测试

问题很简洁,就是LCIS,LCS和LIS的完美结合。确实需要在动态规划上下了很多功夫之后才能做这个题目。也是希望大家不要说我丧心病狂就好。这个问题确实有很多的重叠子问题,目前需要解决的问题就是如何利用这些重叠子问题来记忆化搜索,所以可以用类似LIS和LCS的思想定义一个状态dp(i,j)表示a串前i个元素和b串前j个元素并且以b[j]为结尾构成的LCIS的长度。(希望大家能好好品味一下状态的定义)。之后就是考察这个状态,还是一样如果正想它能转移到哪里实在是天方夜谭,所以反着想,哪些状态能转移过来,这样来看好像只有两种情况了,第一种就是a[i] != b[j],这个状态转移很简单就是dp[i,j] = dp[i-1,j](想一想,为什么?),而第二种a[i] == b[j]的情况,想想转移的话好像有点复杂,没关系,慢慢看,首先第一维i,被拿去和b[j]匹配了,所以肯定不用想了,而i-2这个状态必然没有i-1这个状态好(想一想,为什么?),所以只需要枚举b[1] - b[j-1]中小于b[j]并且LCIS最长的长度,拿过来+1就好。于是得到了状态转移方程:dp[i,j] = (a[i] != b[j]) ? dp[i-1,j] : max(dp[i-1,k]) + 1,其中k是1~j-1的整数并且b[k] < b[j]。当然这是个O(n^3)的算法,对于3000这种数据量来说是无法承受的,所以就需要点小优化,就是在计算过程做到不用枚举k,具体看程序就好了。

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
 
using namespace std;
 
const int maxn = 3000 + 10;
int n;
int a[maxn*2],b[maxn*2];
int f[maxn*2];
 
int main() {
    //freopen("in.in","r",stdin);
    //freopen("out.txt","w",stdout);
    while (cin>>n) {
        for (int i = 1; i <= n; i++) {
            scanf("%d",&a[i]);
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d",&b[i]);
        }
        memset(f,0,sizeof(f));
        for (int i = 1; i <= n; i++) {
            int tmp = 0;
            for (int j = 1; j <= n; j++) {
                if (a[i] > b[j]) tmp = max(tmp,f[j]);
                if (a[i] == b[j]) f[j] = tmp + 1;
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) ans = max(ans,f[i]);
        cout<<ans<<endl;
    }
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值