由数字三角问题来理解DP

先看几类数字三角形的问题,通过对这几个问题的分析来理解有关动态规划的基本思想

数字三角形I

问题描述:

  有一个由正整数组成的三角形,第一行只有一个数,除了最下行之外 每个数的左下方和右下方各有一个数,从第一行的数开始,每次可以往左下或右下走一格,直到走到三角形底端,把沿途经过的数全部加起来作为得分。如何走,使得这个得分尽量大?

分析:

  如何走,是一个决策问题,很容易联想到一种贪心策略是:每次选数字大的那个方向走。然而很明显,这种决策方案是错误的。因为如果按这种方案,得到的结果是1→3→10→3,总和为17,而正确的答案是:1→2→1→20,总和为24,这种方案错在了“目光短线“。

  再次分析,赋予d[i,j]的含义为以(i,j)为首的三角形的最大和,那么这个问题就转换成了求d(1,1)。很明显,d[i,j]=a[i,j]+max(d[i+1,j],d[i+1,j+1])。这里体现的一个思想就是:将大问题分解为小问题,由小问题的求解来得到大问题的答案。

  这个思路是正确的,那么接下来要考虑的一个问题就是如何计算了。有三种方法,递归、递推以及记忆化搜索,接下来就分析这三种计算方法的具体做法。

  方法一:递归

int solve(int i, int j)
{
    if(i == n)
        return a[i][j];
    else
        return a[i][j] + max(solve(i + 1, j), solve(i + 1, j + 1));
}

这个方法存在不必要的重复计算,低效,不推荐使用

  方法二:递推

void solve()
{
    for(int j = 1; j <= n; j++)
        dp[n][j] = a[n][j];
    for(int i = n - 1; i >= 1; i--)
        for(int j = 1; j <= i; j++)
            dp[i][j] = a[i][j] + max(d[i + 1][j], dp[i + 1][j + 1]);
}

递推自底向上,避免了重复计算,时间复杂度为O(n2)

  方法三:记忆化搜索

int solve(int i, int j)
{
    //首先初始化dp为-1 memset(dp,-1,sizeof(dp));
    if(i == n)
        return a[i][j];
    if(dp[i][j] >= 0)
        return dp[i][j];
    dp[i][j] = a[i][j] + max(solve(i + 1, j), solve(i + 1, j + 1));
    return dp[i][j];
}

这个方法就是在递归的基础上加上了记忆化,同样避免了重复计算,时间复杂度为O(n2)

对比这几种方法,可以看出重叠子问题(overlapping subproblems) 是动态规划展示威力的关键

通过这个例子,可以理解动态规划的基本思想:将大问题分解成小问题,建立子问题的描述,建立状态间的转移关系,使用递推或记忆化搜索来实现。而要使用好动态规划的几个关键的重点就是:状态的定义和状态转移方程。

数字三角形II

问题描述:

  在数字三角形I的基础上稍微改变了一下。从第一行的数开始,除了某一次可以走到下一行的任意位置外,每次都只能左下或右下走一格,直到走到最下行,把沿途经过的数全部加起来。如何走,使得这个和尽量大?

分析:

  这题的关键在于后效性问题,即先前的决策可能影响后续的决策,要消除后效性,就得拓展状态定义(加限制条件)来将有后效性的部分包含进去。

 1 memset (maxx, 0, sizeof (maxx));
 2 for(j = 1; j <= n; j ++)
 3 {
 4     d[n][j][1] = d[n][j][0] = a[n][j];
 5     if(a[n][j] > max[n])
 6         maxx[n] = a[n][j];
 7 }
 8 for(i = n - 1; i >= 1; i--)
 9 {
10     for(j = 1; j <= i; j++)
11     {
12         d[i][j][0] = a[i][j] + max(d[i + 1][j][0], d[i + 1][j + 1][0]);
13         if(d[i][j][0] > maxx[i])
14             maxx[i] = d[i][j][0];
15         d[i][j][1] = a[i][j] + max(d[i + 1][j][1], d[i + 1][j + 1][1], maxx[i + 1]);
16     }
17 }

数字三角形III

问题描述:

  在数字三角形I的基础上,问题变成了,如何走,使得这个和的个位数尽量大?

分析:

  符合无后效性了,但是却出现了另一个问题,那就是由子问题的最优解不能推出全局的最优解。比如看下面一个例子

对于灰色格子(2,1)来说,根据状态定义,d[2,1]=6(从 此格子出发路径上数之和的最大个位数),d[2,2]=0(无论怎么走,个位数都是0), 根据前面的“递推方程”算出d[1,1]应是1,但实际上d[1,1]等于9。问题出在:全局最优解5-0-4-0并没有包含子问题最优解0-4-2,即不满足最优子结构。不满足最优子结构的情况通常也可以考虑扩展状态定义。

 1 for(j = 1; j <= n; j++)
 2     d[n][j][a[n][j] % 10] = true;
 3 for(i = n - 1; i >= 1; i--)
 4 {
 5     for(j = 1; j <= i; j++)
 6     {
 7         for(k = 0; k <= 9; k++)
 8         {
 9             d[i][j][k] = false;
10             t = (10 - a[i][j]) % 10;
11             if(d[i + 1][j][t] || d[i + 1][j + 1][t])
12                 d[i][j][k] = true;
13         }
14     }
15 }

原文地址:https://www.cnblogs.com/friend-A/p/10291716.html

时间: 2024-07-29 10:14:52

由数字三角问题来理解DP的相关文章

数字三角形 17 初入dp

Description 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 给出了一个数字三角形.从三角形的顶部到底部有很多条不同的路径.对于每条路径,把路径上面的数加起来可以得到一个和,你的任务就是找到最大的和. 注意:路径上的每一步只能从一个数走到下一层上和它最近的左边的那个数或者右边的那个数. Input 输入数据有多组,每组输入的是一行是一个整数N (1 < N <= 100),给出三角形的行数.下面的N行给出数字三角形.数字三角形上的数的范围都在0和100之间. Output

SCU - 1114 数字三角

下图是个数字三角,请编写一个程序计算从顶部至底部某处一条路径,使得该路径所经过的数字总和最大. 7 3  8 8  1  0 2  7  4  4 1.  每一步可沿左斜线向下或右斜线向下走: 2.  1<=三角形行数<=100 3.  三角形中的数字为整数 0,1,--,99. 4.  如果有多种情况结果都最大,任意输出一种即可. 输入: 第一行一个整数N,代表三角形的行数. 接下来N行,描述了一个数字三角. 输出: 第一行一个整数,代表路径所经过底数字总和. 第二行N个数,代表所经过的数字

SPOJ GNYR09F 数字上的找规律DP

Problem C      SPOJ GNYR09F dp题,dp可能刚刚开始对大家来说比较难,但是静下心来分析还是比较简单的: dp(i ,j ,k)表示前i个数中,当前累积和为j,末尾数字为k的方案数. 考虑第i个位置的2种情况: 1.放0:dp(i,j,0) = dp(i-1,j,0) + dp(i-1,j,1) 2.放1:dp(i,j,1)= dp(i-1,j,0) 因为每一行最开始的状态均从i=j+1,dp(i,j,0)=0,dp(i,j,1)=1开始的,但因为这样子开头均为1,那些

数字三角——递归、递归、内存搜索

数字三角 叙述性说明: 有一个非负整数三角,第一行中只有一个号码,的左下方和右下方各有一个数. 问题: 从第一行的数開始.每次能够往左下或右下走一格.直到走到最下行,把沿途经过的数所有加起来. 怎样走才干使得这个和尽量大? 分析: 不难看出此题是一个动态的决策问题:每次有两种选择--左下或右下. 假设用回溯法求出全部的可能的路线,就能够从中选出最优的路线.但和往常一样,回溯法的效率太低:一个n层数字三角形的完整路线有2^n条.当n非常大时回溯法的速度将让人无法忍受.因此本题讨论用递归,递推及记忆

51Nod 1009 数字1的个数 | 数位DP

题意: 小于等于n的所有数中1的出现次数 分析: 数位DP 预处理dp[i][j]存 从1~以j开头的i位数中有几个1,那么转移方程为: if(j == 1) dp[i][j] = dp[i-1][9]*2+pow(10,i-1);else dp[i][j] = dp[i-1][9]+dp[i][j-1]; 然后注意下对于每个询问统计的时候如果当前位为1需要额外加上他后面所有位数的个数,就是n%pow(10,i-1); 这样总复杂度log(n)*10 #include <bits/stdc++.

51nod 1009 数字1的数量 数位dp

1009 数字1的数量 基准时间限制:1 秒 空间限制:131072 KB 给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数. 例如:n = 12,包含了5个1.1,10,12共包含3个1,11包含2个1,总共5个1. Input 输入N(1 <= N <= 10^9) Output 输出包含1的个数 Input示例 12 Output示例 5 #include<bits/stdc++.h> using namespace std; #define

51nod 1042 数字0-9的数量 (数位dp、dfs、前导0)

1042 数字0-9的数量 基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题 收藏 关注 取消关注 给出一段区间a-b,统计这个区间内0-9出现的次数. 比如 10-19,1出现11次(10,11,12,13,14,15,16,17,18,19,其中11包括2个1),其余数字各出现1次. Input 两个数a,b(1 <= a <= b <= 10^18) Output 输出共10行,分别是0-9出现的次数 Input示例 10 19 Output示例

51nod 1009 - 数字1的数量 - [数位DP][模板的应用以及解释]

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1009 基准时间限制:1 秒 空间限制:131072 KB 给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数. 例如:n = 12,包含了5个1.1,10,12共包含3个1,11包含2个1,总共5个1. Input 输入N(1 <= N <= 10^9) Output 输出包含1的个数 Input示例 12 Output示例

51Nod 1042 数字0-9的数量(数位DP)

题意: 求[l,r]中数字0-9分别出现的次数,11算两次1 思路: 数位dp题解好难写,直接贴代码吧 dp[i]表示[0, 10^i-1]中出现j的次数(按i位补全前导0,显然0-9出现的次数是相同的) 最后再减去每一位出现的前导零即可 代码: #include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<