当前位置:   article > 正文

石子合并问题·区间动规_在一个圆形操场的四周摆放着 n 堆石子。现要将石子有次序地合并成一堆。规定每次

在一个圆形操场的四周摆放着 n 堆石子。现要将石子有次序地合并成一堆。规定每次

题目信息

问题描述: 在一个圆形操场的四周摆放着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 
  • 1
  • 2
5913
24595
  • 1
  • 2

解答

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

#include <iostream>
#include <algorithm>
#include <climits>

#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
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

方法二·化圆为直

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

#include <iostream>
#include <climits>
#include <algorithm>

#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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

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

#define MAX 100

#include <iostream>
#include <climits>
#include <algorithm>

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/509983
推荐阅读
相关标签
  

闽ICP备14008679号