【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火葬场。。。

附上矩阵连乘积的题,是本题的简化版:https://blog.csdn.net/qq_41727666/article/details/83181232

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(无法合并)。

猜你喜欢

转载自blog.csdn.net/qq_41727666/article/details/83184631
今日推荐