DP入门基本问题

个人对简单的dp问题的理解:找是否有重叠问题,明确递推关系,怎么推的(顺序千万不要搞错),找到状态方程,循环时注意边界条件和方程式是否严格成立。

转自:https://www.cnblogs.com/zyx1301691180/p/5727918.html

HDU 2084

Problem Description
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的:
有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

已经告诉你了,这是个DP的题目,你能AC吗?
 
Input
输入数据首先包括一个整数C,表示测试实例的个数,每个测试实例的第一行是一个整数N(1 <= N <= 100),表示数塔的高度,接下来用N行数字表示数塔,其中第i行有个i个整数,且所有的整数均在区间[0,99]内。
 
Output
对于每个测试实例,输出可能得到的最大和,每个实例的输出占一行。
 
Sample Input
1
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
 
Sample Output
30
 
刚看了动态规划,就做了道dp的题目。虽然动态规划还没整明白,但是感觉这道题还是很好理解的。
在做完这道题目之后我又回顾了一下书(算法导论)中所讲:
“我们通常按如下四个步骤来设计一个动态规划的算法:
  1. 刻画一个最优解的结构特征。
  2. 递归地定义最优解的值。
  3. 计算最优解的值,通常采用自底向上的方法。
  4. 利用计算出的信息构造一个最优解。”

我觉得这道题的解题步骤就完全可以用这个来描述:

    1.首先,一个最优解的结构特征,即从顶层走到底层,其各个节点的最大数字之和的为最优解。

    2.题目中要求走法为自顶向下,且每一步只能走到相邻的节点,那么每个节点只有两种选择,即这个节点的两个子节点;

     那么这个节点的最优解就等于这个节点的值加上其两个节点的最优解的最大值;

    3.自底向上的求解。

    4.我们所求出来的解就是我们所需要的答案,则第四步在这里可以忽略掉。

最后附上我的代码:

  我用数组a来接收数塔的值,则第三部可以表示为:a[i][j]的最优解=max(a[i+1][j]的最优解,a[i+1][j+1]的最优解);

而数塔最底层节点的最优解就等于他本身的值,因为他只有一个;那么最底层的值已知,我们只需要从下往上递推就可以了

即这三行代码块:

             for(i=n-1;i>=0;i--)
                 for(j=0;j<=i;j++)
                     a[i][j]=a[i][j]+max(a[i+1][j],a[i+1][j+1]);
 1 #include <bits/stdc++.h>
 2 using namespace std ;
 3 int main ()
 4 {
 5     long long dp[105][105];
 6     int T; cin>>T;
 7     while (T--)
 8     {
 9         int n; cin>>n;
10         memset(dp,0,sizeof(dp));
11         for(int i=0; i<n; i++)
12             for(int j=0; j<=i; j++)
13             cin>>dp[i][j];
14 
15         for(int i=n-1; i>=0; i--)
16             for(int j=0; j<=i; j++)
17             dp[i][j]+=max(dp[i+1][j],dp[i+1][j+1]);
18 
19         cout<<dp[0][0]<<endl ;
20     }
21     return 0;
22 }

 

//用滚动数组优化一下
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

int n;
const int INF=0x3f3f3f3f;
const int maxn=400;
int Map[maxn][maxn], f[maxn];

int main()
{
    //freopen("in.txt", "r", stdin);
    cin>>n;
    for(int i=1; i<=n; i++)
        for(int j=1; j<=i; j++)
            cin>>Map[i][j];

    int ans=-INF;
    for(int i=1; i<=n; i++)
        for(int j=i; j>0; j--)
        {
            f[j]=max(f[j], f[j-1])+Map[i][j];
            if(i==n)
                ans=max(ans, f[j]);
        }

    cout<<ans<<endl;
    return 0;
}

 

很相似的一道题目,但搞了好久。

请保证Chair消灭最多敌人并且冲出包围圈。假设Chair在左上角,达到右下角时算突围成功。

4

1 2 3 4

5 6 9 8

9 1 0 1

2 3 4 5

 

值得注意的是,Chair是如此英勇,应当一往无前,也就是说只能向下或向右走。

图示应为最大战果——于是你应该输出他们的和:35

 1 #include <bits/stdc++.h>
 2 using namespace std ;
 3 int main ()
 4 {
 5     long long dp[105][105];
 6     //int T; cin>>T;
 7     //while (T--)
 8     {
 9         int n; cin>>n;
10         memset(dp,0,sizeof(dp));
11         for(int i=0; i<n; i++)
12             for(int j=0; j<n; j++)
13                 cin>>dp[i][j];
14         /*
15         for(int i=n-1; i>=0; i--)
16             for(int j=0; j<n; j++)
17                 dp[i][j]+=max(dp[i+1][j],dp[i][j+1]);
18         */           //错解,重叠子结构顺序弄反了;
19 
20         for(int i=n-1; i>=0; i--)
21             for(int j=n-1; j>=0; j--)
22                 dp[i][j]+=max(dp[i+1][j],dp[i][j+1]);
23 
24         /*
25        for(int i=0; i<n; i++)
26        {
27             for(int j=0; j<n; j++)
28                  cout<<dp[i][j]<<" ";
29             cout<<endl;
30        }
31        */
32        cout<<dp[0][0]<<endl;
33 
34     }
35     return 0;
36 }

 

今天又遇到一个很相似的题目,a了好久,发现自己跟个智障一样。😂

HDU 2571

yifenfei一开始在矩形的左上角,目的当然是到达右下角的大魔王所在地。迷宫的每一个格子都受到幸运女神眷恋或者痛苦魔王的诅咒,所以每个格子都对应一个值,走到那里便自动得到了对应的值。
现在规定yifenfei只能向右或者向下走,向下一次只能走一格。但是如果向右走,则每次可以走一格或者走到该行的列数是当前所在列数倍数的格子,即:如果当前格子是(x,y),下一步可以是(x+1,y),(x,y+1)或者(x,y*k) 其中k>1。 
为了能够最大把握的消灭魔王lemon,yifenfei希望能够在这个命运大迷宫中得到最大的幸运值。

 

Input
输入数据首先是一个整数C,表示测试数据的组数。
每组测试数据的第一行是两个整数n,m,分别表示行数和列数(1<=n<=20,10<=m<=1000);
接着是n行数据,每行包含m个整数,表示n行m列的格子对应的幸运值K ( |k|<100 )。
 
Output
请对应每组测试数据输出一个整数,表示yifenfei可以得到的最大幸运值。
 
Sample Input
1
3 8
9 10 10 10 10 -10 10 10
10 -11 -1 0 2 11 10 -20
-11 -11 10 11 2 10 -10 -10

 

Sample Output
52

 

和上一题不一样的地方是:1 走法改变了,2 数据有了负数(要么格式化的时候注意,要么写状态转移方程的时候注意)

小结:提交之前要把自己调试写的东西给全注释掉,这次老是PE,很sb;

   写循环的时候尤其要注意循环的条件,不然都不知道错那了;

   对每一题的动态规划的过程要清楚的理解(务必非常清晰)

 1 #include<bits/stdc++.h>
 2 using namespace std ;
 3 int main()
 4 {
 5     int T; cin>>T;
 6     while(T--)
 7     {
 8         int a[25][1005]={0};
 9         int n,m; cin>>n>>m;
10         for(int i=1; i<=n; i++)
11             for(int j=1; j<=m; j++)
12                 cin>>a[i][j];
13 
14         for(int j=m; j>0; j--)
15             a[n][j]+=a[n][j+1];
16 
17         for(int i=n-1; i>0; i--)
18             for(int j=m; j>0; j--)
19             {
20                 int maxn=a[i][j+1];
21                 if(j==m)
22                     maxn=a[i+1][j];
23                 for(int k=2; j*k<m; k++)
24                     maxn=max(a[i][k*j],maxn);
25                 a[i][j]+=max(a[i+1][j],maxn);
26             }
27         
28         /*
29         for(int i=1; i<=n; i++)
30         {
31             for(int j=1; j<=m; j++)
32                 cout<<a[i][j]<<" ";
33             cout<<endl;
34         }
35         */
36 
37         cout<<a[1][1]<<endl;
38 
39     }
40     return 0;
41 }

 

posted @ 2018-12-19 16:13  N_Yokel  阅读(1893)  评论(0编辑  收藏  举报