1. 程式人生 > >【NOJ1148】【DP_動態規劃】石子合併

【NOJ1148】【DP_動態規劃】石子合併

1148.石子合併

時限:1000ms 記憶體限制:10000K  總時限:3000ms

描述

在一個圓形(圓形!!!圓形!圓形!)操場的四周擺放著n堆石子(n<= 100),現要將石子有次序地合併成一堆。規定每次只能選取相鄰的兩堆合併成新的一堆,並將新的一堆的石子數,記為該次合併的得分。編一程式,讀入石子堆數n及每堆的石子數(<=20)。選擇一種合併石子的方案,使得做n-1次合併,得分的總和最小; 比如有4堆石子:4 4 5 9 則最佳合併方案如下: 4 4 5 9 score: 0 8 5 9 score: 8 13 9 score: 8 + 13 = 21 22 score: 8 + 13 + 22 = 43

輸入

可能有多組測試資料。 當輸入n=0時結束! 第一行為石子堆數n(1<=n<=100); 第二行為n堆的石子每堆的石子數,每兩個數之間用一個空格分隔。

輸出

合併的最小得分,每個結果一行。

#include <iostream>

using namespace std;

int n;

int stone[101];

int dp();

int memo[101][101];

int num(int i, int j);  //返回第i堆石子累加到第j堆石子的累加和

int main()
{
    while(cin>>n&&n)
    {
        for(int i=0; i<n; i++)
        {
            cin>>stone[i];
        }

        if(n==1)
        {
            cout<<'0'<<endl;
        }
        else
        {
            cout<<dp()<<endl;
        }
    }
    return 0;
}

int dp()
{
    //備忘錄初始化
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            if(i==j)
            {
                memo[i][j]=0;
            }
            else
            {
                memo[i][j]=INT_MAX;
            }
        }
    }

    //填寫備忘錄(石子踏馬是環形的啊啊啊)
    int minscore=INT_MAX;

    for(int len=1; len<n; len++)
    {
        for(int i=0,j=(i+len)%n; i<n; i++,j=(j+1)%n)
        {
            for(int k=0; k<len; k++)
            {
                memo[i][j]=min(memo[i][j],
                               memo[i][(i+k)%n]+memo[(i+k+1)%n][j]+num(i, j));

                if(len==n-1)    //已經填寫到長度最大的情況
                {
                    minscore=min(minscore, memo[i][j]);    //記錄最優解
                }
            }
        }
    }

    return minscore;
}

int num(int i, int j)   //返回第i堆石子累加到第j堆石子的累加和
{
    int x=0;
    if(i<j)
    {
        for(int k=i; k<=j; k++)
        {
            x+=stone[k];
        }
    }
    else
    {
        for(int k=i; k<n; k++)
        {
            x+=stone[k];
        }
        for(int k=0; k<=j; k++)
        {
            x+=stone[k];
        }
    }
    return x;
}

【後記】

1.剛開始寫的時候以為和矩陣連乘積那道題一模一樣,於是美滋滋地,張藝興的小歌兒聽著,鍵盤劈里啪啦的敲著,敲碼一時爽,WA火葬場。。。

2.然後才發現這石子踏馬的是環形擺著的,不過略一思索,發現只需要擴大for迴圈的遍歷範圍,不管越不越界,多套幾個(…)%n就好了。在矩陣連乘積那道題裡我還講:備忘錄表的下三角用不上、不寫了,結果這道題就用上了。。。

照例上圖:

圖中寫的就是dp函式的工作過程,先初始化,再填表,建議配合程式碼食用。

本題中遞推公式如下,其中k 為從第 i 堆石子開始數的第k個斷開位置,範圍是[0, len-1]:

memo[i][j]=min\limits_{k=0}^{k<len}(memo[(i+k+1)\%n][j]+num(i, j));

我們在下圖的右邊填表過程中可以看到(注意標綠色的數字),每次len++後,新的迴圈都需要填兩個斜行的表,而 i 的範圍總是[0, n-1],j 的範圍總是[ (i+len)%n, (i+len+n-1)%n];

下圖中可以看出,最後的答案有n個,相當於把這個環從n個位置斷開,當作一條線(像矩陣連乘積那道題似的)分別求解而得到的n個最優解,在這些解中選一個代價最小的,就是最終真正的最優解。

memo意義、初始化、填表示意圖

3.本題中還有一個需要注意的地方:單獨處理只有一堆石子(n==1)的情況,此時代價為0(無法合併)。