1. 程式人生 > >乘法遊戲 區間DP 【BOJ221】【Codevs1966】

乘法遊戲 區間DP 【BOJ221】【Codevs1966】

Description

乘法遊戲是在一行N張牌上進行的,每一張牌包括了一個正整數Wi。在每一個移動中,玩家拿出一張牌,得分是用它的數字乘以它左邊和右邊的數,所以不允許拿第1張和最後1張牌。最後一次移動後,這裡只剩下兩張牌。你的目標是使得分的和最小,求最小得分。

Simple input

6
10 1 50 50 20 5

Simple output

3650

Range

3N100.

i,1Wi100.

Analyze

這是一道很典型的區間DP。

解決DP問題,對問題劃分階段,即在滿足無後效性的前提下尋找最優子結構是至關重要的。

對於區間[l,r],要解決的問題是取出[l+1,r1]間的所有數使得得分和最小。區間[l,r]可以劃分為[l,mid] 以及[mid,r]兩個區間。但是,區間[l,r]的問題卻不能簡單劃分為[l,mid] 以及[mid,r]兩個子問題,這是因為兩個區間的決策會互相影響,取出一張牌是無法迅速得知其左、右的牌都是什麼。

例如,對於五張牌1,2,3,4,5,將[1,5]區間的問題劃分成了[1,3][3,5]兩個子區間中的兩個子問題,在處理[1,3]

區間問題時,不知道3這張牌是否已經被取走,無法計算取走2時的得分。

對於區間DP,階段的劃分比區間劃分更為重要,區間的劃分是基於階段的劃分的。如果直接劃分區間,不去思考階段的劃分,問題將難以解決。

什麼叫做階段的劃分呢?就是遵循解決問題的順序,將問題自然而然劃分成多個階段。在此題中,對於區間[l,r],區間中必定有一張牌先取,這張牌將完整區間分成兩部分,完成了第一階段的決策,這便是階段的劃分。

假設對於區間[l,r],其中第一個取走的牌為mid,可以將大區間的問題劃分成[l,mid], [mid,r],對於每個區間恰好滿足左右端點均不能取,是原問題的兩個子問題。但是,這樣的劃分會出現上文中出現過的問題。在左右區間分別處理時,由於mid

已經被取走,我們無法得知mid-1mid+1這些牌的左、右分別是什麼牌。

對於這個問題,可以對階段的劃分做出修改。既然上述問題是由於mid牌已經取走而造成的,那麼不妨設mid是最晚取走的一張牌,這樣的話對於mid劃分的兩個子區間,每個子區間的問題都是大問題的一個子問題,且相互並列,不相干擾。

這樣做的正確性是顯然的,因為對於任意一個區間,總會有最後一張取走的牌。

於是,我們很容易寫出狀態轉移方程:

dp(l,r)=Minmid[l+1,r1]{dp(l,mid)+dp(mid,r)+Wmid×Wl×Wr}

Summary

對於DP問題,應思考解決問題時的順序,根據此順序將問題劃分成子問題。如,尋找解決問題時第一個處理的內容或最後一個,等等。

Source

Codevs 1966
BOJ 221

Code

#include<bits/stdc++.h>
#define gt() getchar()
using namespace std;
const int MAXN = 100 + 7;
int N;
int W[MAXN];
int dp[MAXN][MAXN];
int read()
{
    int res = 0;
    char ch = gt();
    while(ch < '0' || ch > '9')
        ch = gt();
    while(ch >= '0' && ch <= '9')
    {
        res = res * 10 + ch - 48;
        ch = gt();
    }
    return res;
}
int main()
{
    N = read();
    for(int i = 1; i <= N; ++i)
        W[i] = read();

    int r, mid, l, len;
    memset(dp, 127, sizeof(dp));
    for(int i = 1; i <= N - 1; ++i)
        dp[i][i+1] = 0;
    for(len = 3; len <= N; ++len)
    {
        for(l = 1; l <= N - len + 1; ++l)
        {
            r = l + len - 1;
            for(mid = l + 1; mid <= r - 1; ++mid)
                dp[l][r] = min(dp[l][r], dp[l][mid] + dp[mid][r] + W[mid]*W[l]*W[r]);
        }
    }

    printf("%d\n", dp[1][N]);
    return 0;
}