zoukankan      html  css  js  c++  java
  • DP入门(1)——数字三角形问题

    一、问题描述

      如上图所示,有一个由非负整数组成的三角形,第一行只有一个数,除了最下行之外每个数的左下方和右下方各有一个数。现请你在此数字三角形中寻找一条从首行到最下行的路径,使得路径上所经过的数字之和最大。路径上的每一步都只能往左下或右下走。只需要求出这个最大和即可,不必给出具体路径。 三角形的行数大于1小于等于100,数字为 0 - 99 。

    二、问题分析

      要求找出一条路径,它经过的数字之和最大。我们可以细化到每一步,每一次往下走都要选择较大的数。

      于是可以得出下面的以下的伪代码:

    if(当前行是最下行)
    	当前行到最下行的最大和 = 当前数字的值
    else
    	当前行到最下行的最大和 = 下一行到最下行的最大和 + 当前数字的值
    

      这样的伪代码实在是难读,因此我们需要用抽象的方法思考问题(即变量化):

    • (i , j):当前的位置(状态)
    • a(i , j):表示第 i 行的第 j 个数字          //i , j > 0
    • d(i , j):从位置(i , j)到最下行的最大和    //状态(i , j)的指标函数,且原问题的解是d(1,1)

      于是,上面的伪代码转化为:

    if(i == n)
    	d(i,j) = a(i,j);
    else
    	d(i,j) = max{d(i+1,j),d(i+1,j+1)} + a(i,j);
    

      我们来看不同的状态之间是怎么转移的:从位置(i , j)出发有两种决策,①往左走,则走到(i+1 , j)后,将要求解d(i+1 , j);②往右走,则走到(i+1 , j+1)后,将要求解d(i+1 , j+1)。

      由于可以在这两个决策中自由选择,所以应选择d(i+1 , j)和d(i+1 , j+1)中较大的那个。这一步正导出了所谓的状态转移方程:

    • d(i , j)= max{d(i+1 , j), d(i+1 , j+1)} + a(i , j)

      这个方程已经蕴含了最优质结构性质(全局最优解包含局部最优解)。即如果连“从(i+1 , j)或(i+1 , j+1)出发到最下行”这部分的和都不是最大的,加上a(i , j)之后肯定也不是最大的。

    三、解题方式

    1. 递归计算

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

      分析:用直接递归的方法计算状态转移方程,效率往往十分低下。其原因是相同的子问题被重复计算。

    2. 递推计算

    for(int j=1;j<=n;j++)	d[n][j] = a[n][j];		//最后一行
    for(int i=n-1;i>=1;i--)
    	for(int j=1;j<=i;j++)
    		d[i][j] = max(d[i+1][j],d[i+1][j+1]) + a[i][j];
    

      分析:i 是逆序枚举的,所以在计算d[i][j]前,它所需要的d[i+1][j]和d[i+1][j+1]都已经计算出来了。

      提示:可以用递推法计算状态转移方程,递推的关键是边界和计算顺序。

    3. 记忆化搜索

    /*	第一部分:将d全部初始化为-1	*/ 
    memset(d,-1,sizeof(d));	
    /*	第二部分:编写递归函数	*/	
    int solve(int i,int j)
    {
    	if(d[i][j] != -1)	return d[i][j];	    //判断状态(i,j)是否已经被计算过
    	if(i == n)	return d[i][j] = a[i][j];
    	else	return d[i][j] = max(solve(i+1,j),solve(i+1,j+1)) + a[i][j];
    } 
    

      分析:此程序是递归的,但是它同时把计算结果保存在数组d中。所以,千万别忘记在计算之后把它保存在d[i][j]中。此程序的方法称为记忆化,它虽然不像递推法那样显式地指明了计算顺序,但仍然可以保证每个结点只访问一次。

      提示:根据C语言“赋值语句本身有返回值”的规定,可以把保存d[i][j]的工作合并到函数的返回语句中。

      提示:可以用记忆化搜索的方法计算状态转移方程。当采用记忆化搜索时,不必事先确定各状态的计算顺序,但需要记录每个状态“是否已经计算过”。

    四、解题代码

    1. 递归计算

    【第一次错误代码】

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <string>
     4 #include <algorithm> 
     5 #include <cstdlib>
     6 using namespace std;
     7 
     8 int n;
     9 int a[101][101];
    10 
    11 int solve(int i,int j)
    12 {
    13     if(i==n)    return a[i][j];
    14     else    return a[i][j] + max(a[i+1][j],a[i+1][j+1]);        //error
    15 }
    16 
    17 int main()
    18 {
    19     cin>>n;
    20     for(int i=1;i<=n;i++){
    21         for(int j=1;j<=i;j++){
    22             scanf("%d",&a[i][j]);
    23         }
    24     }
    25     int ans = solve(1,1);
    26     printf("%d
    ",ans);
    27     return 0;
    28 }
    View Code

    【第二次正确代码】 

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <string>
     4 #include <algorithm> 
     5 #include <cstdlib>
     6 using namespace std;
     7 
     8 int n;
     9 int a[101][101];
    10 
    11 int solve(int i,int j)
    12 {
    13     if(i==n)    return a[i][j];
    14     else    return a[i][j] + max(solve(i+1,j),solve(i+1,j+1));
    15 }
    16 
    17 int main()
    18 {
    19     cin>>n;
    20     for(int i=1;i<=n;i++){
    21         for(int j=1;j<=i;j++){
    22             scanf("%d",&a[i][j]);
    23         }
    24     }
    25     int ans = solve(1,1);
    26     printf("%d
    ",ans);
    27     return 0;
    28 }
    View Code
    • 分析:提交到poj1163显示TLE,显然递归求解不可行!

    2. 记忆化搜索

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <string>
     4 #include <algorithm> 
     5 #include <cstdlib>
     6 using namespace std;
     7 
     8 int n;
     9 int a[101][101];
    10 int d[101][101];
    11 
    12 int solve(int i,int j)
    13 {
    14     if(d[i][j] != -1)    return d[i][j];
    15     if(i==n)    return d[i][j] = a[i][j];
    16     else    return d[i][j] = a[i][j] + max(solve(i+1,j),solve(i+1,j+1));
    17 }
    18 
    19 int main()
    20 {
    21     cin>>n;
    22     for(int i=1;i<=n;i++){
    23         for(int j=1;j<=i;j++){
    24             scanf("%d",&a[i][j]);
    25         }
    26     }
    27     memset(d,-1,sizeof(d));
    28     int ans = solve(1,1);
    29     printf("%d
    ",ans);
    30     return 0;
    31 }
    View Code

    3. 递推计算

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <string>
     4 #include <algorithm> 
     5 #include <cstdlib>
     6 using namespace std;
     7 
     8 int n;
     9 int a[101][101];
    10 int d[101][101];
    11 
    12 
    13 int main()
    14 {
    15     cin>>n;
    16     for(int i=1;i<=n;i++){
    17         for(int j=1;j<=i;j++){
    18             scanf("%d",&a[i][j]);
    19         }
    20     }
    21     for(int j=1;j<=n;j++)    d[n][j] = a[n][j];
    22     for(int i=n-1;i>=1;i--){
    23         for(int j=1;j<=i;j++){
    24             d[i][j] = a[i][j] + max(d[i+1][j],d[i+1][j+1]);
    25         }
    26     }
    27     printf("%d
    ",d[1][1]);
    28     return 0;
    29 }
    View Code
    • 小结:这方面的代码还不够熟练,继续加强!
  • 相关阅读:
    K8s(2)-部署应用
    Docker-常用命令(7)
    Docker-堆栈stack(6)
    Docker-集群swarm(5)
    Docker-服务(4)
    Docker的概念术语(2)
    k8s(1)-使用kubeadm安装Kubernetes
    Celery-分布式任务队列
    使用Python管理压缩包
    jQuery基础
  • 原文地址:https://www.cnblogs.com/xzxl/p/7492630.html
Copyright © 2011-2022 走看看