乘法遊戲 區間DP 【BOJ221】【Codevs1966】
Description
乘法遊戲是在一行張牌上進行的,每一張牌包括了一個正整數。在每一個移動中,玩家拿出一張牌,得分是用它的數字乘以它左邊和右邊的數,所以不允許拿第1張和最後1張牌。最後一次移動後,這裡只剩下兩張牌。你的目標是使得分的和最小,求最小得分。
Simple input
6
10 1 50 50 20 5
Simple output
3650
Range
Analyze
這是一道很典型的區間DP。
解決DP問題,對問題劃分階段,即在滿足無後效性的前提下尋找最優子結構是至關重要的。
對於區間,要解決的問題是取出間的所有數使得得分和最小。區間可以劃分為 以及兩個區間。但是,區間的問題卻不能簡單劃分為 以及兩個子問題,這是因為兩個區間的決策會互相影響,取出一張牌是無法迅速得知其左、右的牌都是什麼。
例如,對於五張牌1,2,3,4,5
,將區間的問題劃分成了和兩個子區間中的兩個子問題,在處理區間問題時,不知道3
這張牌是否已經被取走,無法計算取走2
時的得分。
對於區間DP,階段的劃分比區間劃分更為重要,區間的劃分是基於階段的劃分的。如果直接劃分區間,不去思考階段的劃分,問題將難以解決。
什麼叫做階段的劃分呢?就是遵循解決問題的順序,將問題自然而然劃分成多個階段。在此題中,對於區間,區間中必定有一張牌先取,這張牌將完整區間分成兩部分,完成了第一階段的決策,這便是階段的劃分。
假設對於區間,其中第一個取走的牌為mid
,可以將大區間的問題劃分成, ,對於每個區間恰好滿足左右端點均不能取,是原問題的兩個子問題。但是,這樣的劃分會出現上文中出現過的問題。在左右區間分別處理時,由於mid
mid-1
,mid+1
這些牌的左、右分別是什麼牌。
對於這個問題,可以對階段的劃分做出修改。既然上述問題是由於mid
牌已經取走而造成的,那麼不妨設mid
是最晚取走的一張牌,這樣的話對於mid
劃分的兩個子區間,每個子區間的問題都是大問題的一個子問題,且相互並列,不相干擾。
這樣做的正確性是顯然的,因為對於任意一個區間,總會有最後一張取走的牌。
於是,我們很容易寫出狀態轉移方程:
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;
}