石子合并问题·区间动规

石子合并问题

  • 题目信息
    • 输入
    • 输出
    • 测试样例
  • 解答
    • 方法一·数组直接进行环形讨论
    • 方法二·化圆为直
    • 方法三·使用四边形不等式优化

题目信息

问题描述: 在一个圆形操场的四周摆放着n堆石子. 现在要将石子有次序地合并成一堆. 规定每次只能选相邻的2堆石子合并成一堆, 并将新的一堆石子数记为该次合并的得分. 试设计一个算法, 计算出将n堆石子合并成一堆的最小得分和最大得分.
算法设计: 对于给定n堆石子, 计算合并成一堆的最小得分和最大得分.

输入

第1行是正整数n,1<=n<=100, 表示有n堆石子. 第2行有n个数, 分别表示n堆石子的个数.

输出

第1行是最小得分, 第2行是最大得分.

测试样例

36
53 49 2 9 9 30 2 35 1 46 39 46 42 33 13 41 35 57 38 59 15 40 18 6 46 30 53 31 34 57 41 20 1 42 59 46 
5913
24595

解答

方法一·数组直接进行环形讨论

#include 
#include 
#include 

#define N 100

using namespace std;

int ans[N][N] = {0};
int s = 0;//所有石子堆的总和(提前计算好)

//计算p[i..j]的石子总和 (由于是环形,j可以小于i)
int calcSum(int p[], int l, int r, int n)
{
    //特殊考虑完整(包含了全部石堆)的情况
    if (l - r == 1 || r - l + 1 == n)
    {
        return s;
    }
    //正常计算
    int sum = 0;
    for (int i = l; i != (r + 1) % n; i = (i + 1) % n)
    {
        sum += p[i];
    }
    return sum;
}

int MINMergeStone(int p[], int n)
{
    int min_ans = INT_MAX;
    for (int l = 2; l <= n; l++)
    {//石子堆的个数:从1到n
        for (int i = 0; i < n; i++)
        {//讨论l个石子的石子堆 p[i..j](由于是环形,j可以小于i)
            int j = (i + l - 1) % n;//用余数巧妙的来表示末尾
            int sum = calcSum(p, i, j, n);//总个数
            ans[i][j] = INT_MAX;
            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和 A[k+1..j]
            for (int k = i; k != j; k = (k + 1) % n)
            {
                ans[i][j] = min(ans[i][k] + ans[(k + 1) % n][j] + sum, ans[i][j]);
            }
            //l = n 即我们需要的答案范围,找出最小值
            if (l == n)
            {
                min_ans = min(ans[i][j], min_ans);
            }
        }
    }
    return min_ans;
}

int MAXMergeStone(int p[], int n)
{
    int max_ans = INT_MIN;
    for (int l = 2; l <= n; l++)
    {//石子堆的个数:从1到n
        for (int i = 0; i < n; i++)
        {//讨论l个石子的石子堆 p[i..j](由于是环形,j可以小于i)
            int j = (i + l - 1) % n;//用余数巧妙的来表示末尾
            int sum = calcSum(p, i, j, n);//总个数
            ans[i][j] = INT_MIN;
            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和 A[k+1..j]
            for (int k = i; k != j; k = (k + 1) % n)
            {
                ans[i][j] = max(ans[i][k] + ans[(k + 1) % n][j] + sum, ans[i][j]);
            }
            //l = n 即我们需要的答案范围,找出最小值
            if (l == n)
            {
                max_ans = max(ans[i][j], max_ans);
            }
        }
    }
    return max_ans;
}

int main()
{
    freopen("E://test.txt", "r", stdin);
    int n;
    cin >> n;
    int p[MAX + 5];
    for (int i = 0; i < n; i++)
    {
        cin >> p[i];
        s += p[i];
    }
    cout << MINMergeStone(p, n) << endl << MAXMergeStone(p, n) << endl;
    return 0;
}

方法二·化圆为直

为方便遍历,可以考虑化圆为直:把圆形剪开——假设石子堆为1、2、3,那么剪开的石子堆为1、2、3、1、2,那么如果原数组 p 长度为 n, 那么经过我们的剪开操作,长度变为 2n - 1。接下来就是和线型的合并石子问题一样了。
唯一的区别是:对于线型的2n - 1 个石子堆,我们的结果不是选取 L = 2n - 1的,而是 L= n 中所有结果的最小值。

#include 
#include 
#include 

#define MAX 100

using namespace std;

int ans[2 * MAX + 5][2 * MAX + 5] = {0};

//将环形的石子化为线形
void GetList(int p[], int n)
{
    int j = 0;
    for (int i = n; i < 2 * n - 1; i++)
    {
        p[i] = p[j++];
    }
}

int MINMergeStone(int p[], int n)
{
    int min_ans = INT_MAX;
    int N = 2 * n - 1;//线形中石子堆个数看做 2n-1
    //石子堆的个数:从1到n
    for (int l = 2; l <= n; l++)
    {
        //讨论l个石子的石子堆p[i..j]
        for (int i = 0; i < N - l + 1; i++)
        {
            int j = i + l - 1;
            //计算石子堆p[i..j]的总数
            int sum = 0;
            for (int t = i; t <= j; t++)
                sum += p[t];

            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和 A[k+1..j]
            ans[i][j] = INT_MAX;
            for (int k = i; k < j; k++)
                ans[i][j] = min(ans[i][k] + ans[k + 1][j] + sum, ans[i][j]);
            //l=n 即我们需要的答案范围,找出最小值
            if (l == n)
            {
                min_ans = min(ans[i][j], min_ans);
            }
        }
    }
    return min_ans;
}

int MAXMergeStone(int p[], int n)
{
    int max_ans = INT_MIN;
    int N = 2 * n - 1;//线形中石子堆个数看做 2n-1
    //石子堆的个数:从1到n
    for (int l = 2; l <= n; l++)
    {
        //讨论l个石子的石子堆p[i..j]
        for (int i = 0; i < N - l + 1; i++)
        {
            int j = i + l - 1;
            //计算石子堆p[i..j]的总数
            int sum = 0;
            for (int t = i; t <= j; t++)
                sum += p[t];

            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和A[k+1..j]
            ans[i][j] = INT_MIN;
            for (int k = i; k < j; k++)
                ans[i][j] = max(ans[i][k] + ans[k + 1][j] + sum, ans[i][j]);
            //l=n 即我们需要的答案范围,找出最小值
            if (l == n)
            {
                max_ans = max(ans[i][j], max_ans);
            }
        }
    }
    return max_ans;
}

int main()
{
    freopen("E://test.txt", "r", stdin);
    int n;
    cin >> n;
    int p[2 * MAX + 5];
    for (int i = 0; i < n; i++)
    {
        cin >> p[i];
    }
    GetList(p, n);//化圆为直
    cout << MINMergeStone(p, n) << endl << MAXMergeStone(p, n) << endl;
    return 0;
}

方法三·使用四边形不等式优化

#define MAX 100

#include 
#include 
#include 

using namespace std;

int ans[2 * MAX + 5][2 * MAX + 5] = {0};
int divide[2 * MAX + 5][2 * MAX + 5] = {0};

//将环形的石子化为线形
void GetList(int p[], int n)
{
    int j = 0;
    for (int i = n; i < 2 * n - 1; i++)
    {
        p[i] = p[j++];
    }
}

void initDivideArray(int n)
{
    for (int i = 0; i < n; i++)
    {
        divide[i][i] = i;
    }
}

int MINMergeStone(int p[], int n)
{
    int min_ans = INT_MAX;
    int N = 2 * n - 1;// 线形中石子堆个数看做 2n - 1
    initDivideArray(N);  //初始化divide数组

    //石子堆的个数:从1到n
    for (int l = 2; l <= n; l++)
    {
        //讨论l个石子的石子堆 p[i..j]
        for (int i = 0; i < N - l + 1; i++)
        {
            int j = i + l - 1;
            //计算石子堆p[i..j]的总数
            int sum = 0;
            for (int t = i; t <= j; t++)
            {
                sum += p[t];
            }

            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和 A[k+1..j]
            ans[i][j] = INT_MAX;
            for (int temp, k = divide[i][j - 1]; k <= divide[i + 1][j]; k++)
            {
                temp = ans[i][k] + ans[k + 1][j] + sum;
                if (temp < ans[i][j])
                {
                    ans[i][j] = temp;
                    divide[i][j] = k;
                }
            }

            //l = n 即我们需要的答案范围,找出最小值
            if (l == n)
            {
                min_ans = min(ans[i][j], min_ans);
            }
        }
    }
    return min_ans;
}

int MAXMergeStone(int p[], int n)
{
    int max_ans = INT_MIN;
    int N = 2 * n - 1;//线形中石子堆个数看做 2n - 1
    initDivideArray(N);//初始化divide数组

    //石子堆的个数:从1到n
    for (int l = 2; l <= n; l++)
    {
        //讨论l个石子的石子堆 p[i..j]
        for (int i = 0; i < N - l + 1; i++)
        {
            int j = i + l - 1;
            //计算石子堆p[i..j]的总数
            int sum = 0;
            for (int t = i; t <= j; t++)
            {
                sum += p[t];
            }

            //依次讨论每一个分割点d:将石子堆p[i..j]分成p[i..k]和 A[k+1..j]
            ans[i][j] = INT_MIN;
            for (int temp, k = i; k < j; k++)
            {
                temp = ans[i][k] + ans[k + 1][j] + sum;
                if (temp > ans[i][j])
                {
                    ans[i][j] = temp;
                    divide[i][j] = k;
                }
            }

            //l = n 即我们需要的答案范围,找出最大值
            if (l == n)
            {
                max_ans = max(ans[i][j], max_ans);
            }
        }
    }
    return max_ans;
}

int main()
{
    freopen("E://test.txt", "r", stdin);
    int n;
    cin >> n;
    int p[2 * MAX + 5];
    for (int i = 0; i < n; i++)
    {
        cin >> p[i];
    }
    GetList(p, n);//化圆为直
    cout << MINMergeStone(p, n) << endl << MAXMergeStone(p, n) << endl;
    return 0;
}

你可能感兴趣的:(动态规划,算法)