1. 程式人生 > >學習動態規劃DP(一)——DAG模型

學習動態規劃DP(一)——DAG模型

之前初學了一點關於動態規劃的知識,但沒有系統的學習,最近在空閒時間根據紫書(演算法競賽入門經典)開始了比較有計劃的學習,先寫下這篇部落格,作為筆記。

一、我對動態規劃的看法。
動態規劃,即是把原問題劃分為各個規模更小的問題去解決,原問題的最優解包括了各個子問題的最優解(感覺本質上是分治法)。所以動態規劃用於求解最優值,解題過程中最重要的是確定好狀態轉換方程。對於動態規劃的學習,我覺得需要知道的基礎概念不多,就是了解了動態規劃的想法,然後就是做題訓練了,從題目中學習DP才比較有效率。

二、一般的解題思路、步驟。
1、定義狀態:一個狀態遵循什麼規則需要我們自己定義,我們可以通過定義不同的規則來定義不同的狀態,但要寫出每一個狀態的轉移方程的難度不同,所以需要我們有目的的篩選,這就需要從題目訓練中學習。通過量的累積達到質的變化。
2、判斷一個狀態有幾種可能的決策:在遵循自己對狀態所定義的規則的條件下,確保對條件的列舉不存在缺漏的情況下,列舉出某一狀態下可能出現的不同決策,即各種可能的狀態轉變方向。
3、根據不同的決策列出對應的狀態轉移方程:狀態轉移方程需要確保可以得到最優的結果並且對各種可能出現的情形沒有漏舉的情況。

三、如何定義狀態
做了一些題,我發現對狀態的定義,我們都是用陣列來表示,可能是一維陣列,也可能是多維陣列。陣列的維度根據不同的題目要求而定,有時可以有多解,就是可以用不同維度的陣列來解題,但這種情況下一般多維的解題陣列可以降維到最簡。下面還是通過一些基礎、經典的例題來說明。

四、DP例題(一下兩題都是紫書的,暫時找不到具體來源)
DAG模型(DAG——有向無環圖):即是把動態規劃的題目的解題轉為有向無環圖去解題。
1、矩形巢狀
題目描述:有n個矩形,每一個矩形可以用兩個整數a、b描述表示長和寬,如矩形X(a,b)。矩形可以90°旋轉。當一個矩形的長和寬都分別嚴格小於另一個矩形的長和寬時,該矩形可以巢狀在另一個矩形之中。如(1,5)可以巢狀在(2,6)中,但不可以巢狀在(3,4)中。要求選出儘量多的矩形排成一行,使得除了最後一個之外,每一個矩形都可以巢狀在下一個矩形之內。如果有多解,矩形編號的字典序應該儘量的小。

分析:一個矩形要巢狀在另一個矩形中需要有一點的條件(前一個矩形較長的一條邊小於後一個矩形的較長的一條邊,以及前一個矩形較短的一條邊小於後一個矩形的較短的一條邊),這樣可以把前後兩個矩形看成兩個不同的節點,前一個矩形巢狀在後一個矩形裡,看成有一條從後一個矩形指向前一個矩形的有向邊。這樣就可以把問題轉換為求解有向圖的最長路且這個有向圖是無環的,即DAG。
要求最長路,我們可以定義一個狀態,如 d[i] 表示從結點 i 出發的最長路長度,這樣的話可以求出,狀態轉移方程為 d[i] = max(d[i],d[j]+1),其中 j 是結點 i 的下一個結點,即存在一條有向邊從 i 指向 j。
為什麼狀態轉換方程是這樣呢?首先在每一個當前狀態下,我們只有一種決策可行,就是前往下一個,即使下一個點有多種選擇,但本質上都一樣,就是要前往下一個點。然後將當前狀態和下一個狀態比較,d[i]表示從結點 i 出發的最長路徑,因為存在從 i 到 j 的有向邊,所以如果從結點 j 出發的最長路徑加上從 i 到 j 之間的路徑的和得到的新的路徑比原來從結點 i 出發的最長路徑還長,那麼我們就更新從結點 i 出發的最長路徑長度,也就得出了上面的狀態轉移方程。因為每一個結點我們都和與它相連的結點相比較,所以最終得出的是最優結果。
(關於狀態的選取,我覺得還是需要多做題從題目中學習,以後自己做的時候才會有比較清晰的思路)

程式碼:

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const int MAXN=1e3+5;
// g[i][j]為鄰接矩陣,表示一張圖,這張圖上有從 i 到 j 的有向邊
// d[i]為狀態陣列
int g[MAXN][MAXN],d[MAXN],n;

//記錄矩形的資訊
struct Rec
{
    int x,y;
} rec[MAXN];

int DP(int i)
{
    //記憶化搜尋,如果該點已經計算過就不用再算,防止重複運算,在一些資料大的題中可能會導致超時
    if(d[i]>0) return d[i];
    for(int j=1; j<=n; j++) //遍歷i的所有可達出邊
        if(g[i][j]) //判斷是否有從 i 到 j 的有向邊
            //注意max(,)中後者不是 d[j]+1,而是DP(j)+1,因為這時候 d[j]可能還未計算,所以我們用是的DP(j),
            //因為DP函式的作用就是要來求出 d[j] 的值的。
            d[i]=max(d[i],DP(j)+1);
    return d[i];
}
//按最小字典序輸出最優決策下的矩形編號
//選取最大的 d[i] 對應的 i ,如果有多個 i,則選擇最小的i 才能保證字典序最小 
void print(int i)
{
    cout<<i<<' ';
    for(int j=1; j<=n; ++j)
    {
        //當 i 和 j 之前存在有向邊並且在最長路徑上,j 是 i 的下一個點,則輸出 j。 
        if(g[i][j]&&d[i]==d[j]+1)
            print(j);
        break;
    }
}
//檢查是否滿足巢狀條件
int check(Rec r1,Rec r2)
{
    if((r1.x<r2.x&&r1.y<r2.y)|| (r1.x<r2.y&&r1.y<r2.x))
        return 1;
    return 0;
}

int main()
{
    int i,m,j;
    cin>>m;//例子數
    while(m--)
    {
        memset(d,0,sizeof(d));
        memset(g,0,sizeof(g));
        cin>>n;
        for(i=1; i<=n; i++)
            cin>>rec[i].x>>rec[i].y;
        // 確定哪兩個點之間有邊連線
        for(i=1; i<=n; i++)
            for(j=1; j<=n; j++)
                if(i!=j)
                    g[i][j]=check(rec[i],rec[j]);
        DP(1);
        int ans=-1,dex;
        for(i=1; i<=n; i++)
            if(ans<d[i])
            {
                ans = d[i];
                dex = i;
            }   
        //因為陣列 d[i] 求出的是從結點 i 出發的最長路,計算的是一條路徑上的邊數,所以答案需要 +1 得到總結點數 
        cout<<ans+1<<endl; 
        print(dex);
    }
}

2、硬幣問題
題目描述:有 n 種硬幣,面值分別為V1,V2,…,Vn,每種都有無限多,給定非負整數S,可以選用多少個硬幣使得面值之和恰好為S?輸出硬幣數目的最小值和最大值。1<=n<=100,0<=S<=10000,1<=Vi<=S。

分析:這型別的題其實也可以用DAG模型去解題。如我們把每種面值看作一個點,表示“還需要湊足的面值”。比如 d[i] 中 i 表示還需要湊足 i 元,而整個 d[i]陣列,我們可以表示湊足 i 需要多少硬幣。故初始狀態為 d[S],目標狀態為d[0]。(初學時覺得很巧妙,用模型解題,思路會比較清晰)。
接下來確定狀態轉移方程。同樣的,我們要明確在每一個狀態下,下一種狀態有多少中決策。因為我們有各種面值的硬幣,所以把這些面硬幣根據輸入的順序編號,選擇面值小於我們當前所需面值的硬幣來湊足我們需要的面值。比如我們使用了第 j 個硬幣,那麼d[i] 的下一個狀態就是
d[ i - V[j] ] + 1。( +1 ,加上使用的面值為 V[j] 硬幣),由於題目要求最大和最小數目,所以我們把當前狀態與下一個狀態比較,分別取最大值和最小值即可。因此,求最大值狀態轉移方程為:d[i] = max(d[i],d[i-V[j]]+1),最小值為:d[i] = min(d[i],d[i-V[j]]+1)。

下面給出求最大值,以及同時求最大和最小值的核心程式碼

//最長路
int dp_max(int s)// 第一個 s 是題目中的 S。 
{
    //記憶化搜尋已經計算過的點不用重複計算
    if(vis[s])
        return d[s];
    //標記該結點已經訪問過 
    vis[s] = 1;
    //因為題目中 S 可以為 0,所以初始將 d[i]設定為正常情況下取不到的負數。 
    d[s]=-1000;
    for(int i=1; i<=n; ++i)
        // 如果硬幣面值小於目前還需要湊足的面值才進行下一步狀態轉換 
        if(s>=v[i])
            d[s] = max(d[s],dp_max(s-v[i])+1);
    return d[s];
}

//同時求最大最小值 
void max_min()
{
    // 先把陣列初始化為不能取到的值,min 和 max 函式才有效
    memset(minv,INF,sizeof(minv));
    memset(maxv,-INF,sizeof(maxv));
    //如果需要湊足的面值為 0,那麼當然不需要任何硬幣 
    maxv[0] = minv[0] = 0; 
    //依次計算每一種面值需要的硬幣數 
    for(int i=1; i<=s; ++i)
        for(int j=1; j<=n; ++j)
            if(i>=v[j])
            {
                // 當前狀態 與 如果選擇了當前的硬幣 v[j] 後的狀態比較
                // 因為對任意錢數,都是由已有的硬幣湊成
                // 對任意錢數都選擇最少或最多的組成,再往上湊即也是最少或最多
                maxv[i]=max(maxv[i],maxv[i-v[j]]+1);
                minv[i]=min(minv[i],minv[i-v[j]]+1);
            }
    cout<<maxv[s]<<endl<<minv[s]<<endl;
    //也可以在計算最大值最小值的同時記錄下每種最優決策對應取的硬幣編號
    /*if(maxv[i]<maxv[i-v[j]]+1)
    {
        maxv[i]=maxv[i-v[j]]+1;
        max_coin[i]=j;
    }
    if(minv[i]>minv[i-v[j]]+1)
    {
        minv[i]=minv[i-v[j]]+1;
        min_coin[i]=j;
    }*/
}

小結:
個人感覺學習動態規劃,涉及的知識挺多的,最好是一次學習一種題型,然後自己做好歸納。比如這類題型的狀態一般怎麼定義,可以用什麼模型去做等。比如上面的兩道例題都是跟DAG最長最短路有關,對於這種題型,一般可以定義兩種狀態:
(1)設d[i]為從 i 出發的最長路,則d[i]=max{d[j]+1,d[i]}((i,j)∈E)。
(2)設d[i]為以 i 結束的最短路,則d[i]=max{d[j]+1,d[i]}((j,i)∈E)。
然後就是刷題,題做多了,我感覺對動態規劃的理解會自然而然的深。

相關推薦

學習動態規劃DP——DAG模型

之前初學了一點關於動態規劃的知識,但沒有系統的學習,最近在空閒時間根據紫書(演算法競賽入門經典)開始了比較有計劃的學習,先寫下這篇部落格,作為筆記。 一、我對動態規劃的看法。 動態規劃,即是把原問題劃分為各個規模更小的問題去解決,原問題的最優解包括了

動態規劃入門

spa turn color and uil ott c++ erro 大數字 2017-09-01 11:29:43 writer:pprp 看sprout臺灣大學acm教學視頻的第一部分: 裏邊涉及到四道小例題 感覺很好就拿來寫了寫: 題意還有代碼說明都在代碼中: 1、

LeetCode-動態規劃總結

動態規劃 遞迴和動態規劃都是將原問題拆成多個子問題然後求解,他們之間最本質的區別是,動態規劃儲存了子問題的解,避免重複計算。 斐波那契數列 爬樓梯 70. Climbing Stairs (Easy) 題目描述:有 N 階樓梯,每次可以上一階或者兩階,求有多少種上樓梯

動態規劃演算法---

摘自網路: ———————————— 題目: 有一座高度是10級臺階的樓梯,從下往上走,每跨一步只能向上1級或者2級臺階。要求用程式來求出一共有多少種走法。 比如,每次走1級臺階,一共走10步,這是其中一種走法。我們可以簡寫成 1,1,1

遞迴和動態規劃專題----劍指offer+左程雲演算法

遞迴和動態規劃專題(一)–劍指offer+左程雲演算法 (一).斐波那契專題 【題目】大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項。 n<=39   毫無疑問,大家能想到這個公式:F(n)=F(n-1)+F

算法學習 —— 動態規劃練習

ESS 初始化 並且 輸出 lib program 可能 type 參考 一、不同路徑(LeetCode-62) 1.1 題目介紹 62. 不同路徑 1.2 解題思路 計數型動態規劃 最後一步 最右下角的坐標假設為(m,n),則假設走到(m,n)所有可能的路徑為f[m][

【機器學習+sklearn框架】 線性模型之Linear Regression

前言 一、原理    1.演算法含義    2.演算法特點 二、實現   1.sklearn中的線性迴歸   2.用Python自己實現演算法 三、思考(面試常問) 參考 前言        線性迴歸(Linear Regression)基本上可以說是機器

動態規劃系列1——金礦模型的理解

本文主要總結了引文中提出的金礦模型的思考方法,並用python程式碼來實現演算法,從而加深了對動態規劃思想的理解。 1. 故事描述 注:內容節選自開篇引文。 有一個國家,所有的國民都非常老實憨厚,某天他們在自己的國家發現了十座金礦,並且這十座金礦在地圖

動態規劃學習系列——區間DP

上一篇我們看了區間型DP的一道經典入門題——石子歸併,這一次同樣是類似的一道題——石子歸併2 題目連結:wikioi 2102 題幹不同之處在於,現在我們的石子不是排成一列了,而是圍成一個環,我們要怎麼把問題轉化成普通的石子歸併呢? 其實這是一種挺常見的演

軟考網路規劃設計師教程學習筆記第一章

第1章計算機網路原理 1.1計算機網路概論(P1-10) 1、定義與應用 計算機網路是一個將分散的、具有獨立功能的計算機系統,通過通訊裝置與線路連線起來,由功能完善的軟體實現資源共享的系統。 計算機網路的幾個應用方向:對分散的資訊進行集中、實時處理;共享資源;電子化辦公與服務

動態規劃專題——斜率優化DP

前言 斜率優化\(DP\)是難倒我很久的一個演算法,我花了很長時間都難以理解。後來,經過無數次的研究加以對一些例題的理解,總算啃下了這根硬骨頭。 基本式子 斜率優化\(DP\)的式子略有些複雜,大致可以表示成這樣: \[f_i=min_{j=1}^{i-1}(A(j)-B(j)*S(i)+C(i)

動態規劃專題——樹形DP

前言 DPDPDP這東西真的是博大精深啊… 簡介 樹形DPDPDP,顧名思義,就是在樹上操作的DPDPDP,一般可以用fif_ifi​表示以編號為iii的節點為根的子樹中的最優解。 轉移的時候一般都將

動態規劃專題——數位DP

前言 數位DPDPDP 真的是最噁心的DPDPDP。 簡介 看到那種給你兩個數,讓你求這兩個數之間符合條件的數的個數,且這兩個數非常大,這樣的題目一般就是 數位DPDPDP 題。 數位DPDPDP一般

c++使用樸素遞迴演算法自頂向下遞迴動態規劃dp帶備忘的自頂向下,自底向上解決鋼條切割及執行例項結果

本博文資料來源於演算法導論第三版 動態規劃有兩種等價實現方法:帶備忘的自頂向下發(topDownWithMemoization),自底向上方法,付出額外的記憶體空間來節省計算時間,是典型的時空權衡,遞迴時會儲存每個子問題的解 長度n與對應價格p關係 1~10的對應最

caioj1063·動態規劃入門維一邊推1:美元和馬克

1063:動態規劃入門(一維一邊推1:美元和馬克) 時間限制: 1 Sec 記憶體限制: 128 MB 題目描述 【問題描述】 今天6:00起床,我轉身發現枕頭邊有100美元。 出門的時候發現門口有家冰淇淋店,拉了很長的橫幅:“今天100美元和4

動態規劃入門DP 基本思想 具體實現 經典題目 POJ1088

(一) POJ1088,動態規劃的入門級題目。嘿嘿,連題目描述都是難得一見的中文。 題目分析: 求最長的滑雪路徑,關鍵是確定起點,即從哪開始滑。 不妨設以( i, j )為起點,現在求滑行的最長路徑。 首先,( i, j )能滑向的無非就是它四周比它低的點。到底滑向哪個點?

動態生成頁面——ASP.NET中Literal使用

case colspan label 奇偶數 容器 業務邏輯 con stringbu font 在頁面中加入內容時,假設是靜態內容。無需使用容器,能夠直接將標記作為HTML直接加入到頁面中;可是,假設是動態內容,則必須借助容器將內容加入到頁面中。典型的容器

監督式學習 -- 分類決策樹

cte 求解 分支 基本概念 tracking 它的 解決 mat 這就是 決策樹(decision tree)是一種基本的分類與回歸方法。其表示的樹型結構,能夠覺得是if-else規則的集合。基本的長處是分類可讀性好,速度快。一般會有三個步驟:特征選擇、決策樹的生成

統計學習基本理論知識

求解 兩個 向量 定義 標準差 註意 begin lan 語言模型 本篇將依據《統計自然語言處理》(宗成慶),重新梳理統計學習相關理論知識,相關概率論與梳理統計的課本不再列出來,可以找任意相關的課本復(預)習。 概率 概率是表示事件發生的可能性,將隨機試驗中的事件映射到實數

Django重新開始學習--環境搭建 筆記

nbsp url對應 tin art admin set pla base default 環境 :   python2.7   django 1.9.8   使用工具pycharm   Mysql =====================================