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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM