DP(动态规划)个人学习-初步

动态规划是一种重要算法,其思想,在我个人理解来看,就是把问题逐步地进行简化,从一个看似复杂的问题,将问题化小,简化到可以用某条公式(状态转移方程)逐步求出每种状态的解,直到最后求出所要的答案。

比如01背包问题,背包容量有限,要尽可能装入价值大的物品,我们可以假设一个dp数组,其中dp[j]表示在容量为j的情况下最多能装入多少价值的物品,这样就把问题简化到求一个个状态了,但是明显还是不够用来解决问题,那么怎么办呢?想想:对于每一种物品,都有装入和不装入两种选择,于是多了这么一中约束条件:装\不装。又由于每种物品只有一件,所以应该从后往前更新,保证每种物品只被装入一遍,这样一步步推下来,就得到了我们所要的状态转移方程:

dp[j] = max{ dp[j-w[i]]+v[i], dp[i+1] }

具体代码则要写成这样:

for(int i = 0; i < n ;i++)
    for(int j = W; j >= w[i]; j--)
        dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
好像有点难以理解?其实就是一种数学思想,做动态规划要对状态的转移有足够清晰的思路,最后还得回到
做题上。下面是HDU上一个关于DP的题目组,要掌握好各种算法最最最主要的还是多做题!

http://acm.hdu.edu.cn/problemclass.php?id=516&page=1

下面先上一道最简单的DP题:HDU-1003  Max Sum

题目意思就是给一串数字,要你求出其中和最大的子序列,输出最大的和,子序列的起始和结束点。

如何做这道题呢?首先简化下问题,我们可以求到每个数字时以这个数字为结尾的最大子序列和是多少,于是dp数组出来了,接下来,如何实现状态转移?考虑这样一件事实:对于每一个a[i],dp[i-1]都有加或不加的权利,如果加上他能使其接近答案,那么就加上。怎样才算接近答案呢?如果该dp[i-1]大于0,那么无疑可以加上,不管加上后是正是负在以后的状态转移中再来筛除。如果dp[i-1]小于0,那么这一段可以舍弃掉了,直接从当前数字起始,即dp[i] = a[i]。方程出来了!

上面说的有点繁琐,主要为了初学者和自己便于理解,具体见代码,不正确的地方还请大牛指正!

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
int a[100000+20], dp[100000+20];

int main()
{
    int T, n, j = 1;
    scanf("%d", &T);
    while(T--) {
        int left = 0, right = 0, maxn;
        memset(dp, 0, sizeof(dp));
        scanf("%d", &n);
        for(int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        maxn = dp[0] = a[0];
        // 根据状态转移方程写出dp数组,同时记录最大值,dp[i]最大时即在此时应该记录的右值为i
        for(int i  = 1; i < n; i++) {
            dp[i] = dp[i-1] > 0 ? dp[i-1]+a[i] : a[i];
            if(maxn < dp[i]) {
                maxn = dp[i];
                right = i;
            }
        }
        left = right;  // 这一句非常重要,漏了这句WA了十几次,因为要考虑到子序列是一个数的结果,这个初始化不能少
        for(int i = left-1; i >= 0; i--) {  // 从右值开始向前推知道找到这个子序列的左值
            if(dp[i] >= 0)
                left = i;
            else
                break;
        }
        printf("Case %d:\n%d %d %d\n", j++, maxn, left+1, right+1);
        if(T)
            printf("\n");
    return 0;
    }
}


接下来是1003的升级版:HDU-1024  Max Sum Plus Plus

题目意思与上面差不多,改变的是这次要求多段子序列和最大。

这次加了个条件,求n段的子序列的最大和。如何入手呢?首先先写一下二维的dp方程:

dp[i][j] = max{dp[i][j-1], dp[i-1][k]} + s[i]


dp[i][j]表示前j个数字的i个子序列最大和是多少,很好,状态转移方程不难写出,接下来考虑一下如何优化?二维的写法我也不是很会写,能化成一维就更好了。思考一下:在我们的方程中,用到了什么?其实也只有当前状态(dp[i][j-1])和上一个状态(dp[i-1][k])而已!于是可以用两个一维数组表示。

#include <iostream>
#include <cstdio>
#include <cstring>

typedef long long ll;
using namespace std;
int s[1000000+20];
ll cur[1000000+20], pre[1000000+20];

int main()
{
    int m, n, j;
    ll maxsum;
    while(~scanf("%d%d", &m, &n)) {
        for(int i = 1; i <= n; i++) {
            scanf("%d", &s[i]);
        }
        memset(cur, 0, sizeof(cur));  // 当前状态
        memset(pre, 0, sizeof(pre));  // 上一次状态
        for(int i = 1; i <= m; i++) {  // 从第一个子段开始
            maxsum = -1e9;  // 每次最大值复位
            for(j = i; j <= n; j++) {  // i子段状态上推下下一个子段
                cur[j] = max(cur[j-1], pre[j-1]) + s[j];  //方程
                pre[j-1] = maxsum;  // 记录上一个状态
                maxsum = max(maxsum, cur[j]);  // 最大值
            }
            pre[j-1] = maxsum;
        }
        printf("%lld\n", maxsum);
    }
    return 0;
}

未完待更。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值