1. 程式人生 > >經典問題二.【區間dp】石子歸併 51nod 1021

經典問題二.【區間dp】石子歸併 51nod 1021

51nod 1021 石子歸併(區間dp)
問題描述:
N堆石子擺成一條線。現要將石子有次序地合併成一堆。規定每次只能選相鄰的2堆石子合併成新的一堆,並將新的一堆石子數記為該次合併的代價。計算將N堆石子合併成一堆的最小代價。
括號裡面為總代價可以看出,第一種方法的代價最低,現在給出n堆石子的數量,計算最小合併代價。

思路:
區間dp通過區間的關係操作,從小區間到大區間。區間dp感覺有點像分治一樣的,我們知道如果有兩個數的話,那麼其值就為兩數相加,如果有大於兩個的數,那麼通過區間的合併的最優解能得到答案。
dp表示含義,與遞推式,已程式碼註釋。
AC程式碼
版本一:


#include <cstdio>
#include <cstring> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; int dp[210][210]; //代表 區間i到j合併成一堆最小代價。 int p[210][210]; //代表 區間i到j的和 int a[210]; int n; void init() { for(int i = 1; i <= n; i++) { int sum = 0; for(int j = i; j <= n; j++) { sum += a[j]; p[i][j] = sum; } } } int
main() { while(~scanf("%d",&n)) { for(int i = 1; i <= n; i++) scanf("%d",&a[i]); init(); // 小區間 到 大區間 // dp[i][j] = dp[i][k]+dp[k+1][j]+a[k]+a[k+1]; memset(dp,0,sizeof(dp)); for(int len = 1; len < n; len++) { for
(int i = 1; i+len <= n; i++) { int r = i, l = i+len; dp[r][l] = INF; for(int k = r; k < l; k++) dp[r][l] = min(dp[r][l],dp[r][k]+dp[k+1][l]+p[r][k]+p[k+1][l]); } } printf("%d\n",dp[1][n]); } return 0; }

版本二:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define N 110

int a[N];
int dp[N][N];
int sum[N];
int main()
{
    int n;
    scanf("%d",&n);
    memset(dp,0,sizeof(dp));
    memset(sum,0,sizeof(sum));
    for(int i = 0; i < n; i++)
    {
        scanf("%d",&a[i]);
        sum[i] = i==0?a[i]:a[i]+sum[i-1];
    }
    for(int i = n-1; i >= 0; i--)
    {
        for(int j = i; j < n; j++)
        {
            if(i == j) dp[i][j] = 0;
            else
            {
                dp[i][j] = INF;
                int x = sum[j] - (i==0?0:sum[i-1]);
                for(int k = i; k <= j; k++)
                    dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j]+x);
            }
            //printf("%d ",dp[i][j]);
        }
        //puts("");
    }
    printf("%d\n",dp[0][n-1]);
    return 0;
}