问题描述: 在一个圆形操场的四周摆放着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;
}