有n堆石子放在路边一列,现在要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)
一个圆形操场周围摆放着n堆石子圆圈,现在要将石子有序地合并成一堆,规定每次只能移动相邻的两堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费(最小或最大)
如果n-1次合并的全局最优解包含了每一次合并的子问题的最优解,那么经这样的n-1次合并后的花费总和必然是最优的。
如果把路边玩法看成石子合并问题,那么操场玩法就属于圆形石子合并问题。圆形石子合并经常转换为直线型来求。也就是说,把圆形结构看成是长度为原规模两倍的直线结构来处理,如果操场玩法的规模为n,所以相当于有一排石子 a1, a2, … an, a1, a2, …, an-1,该问题规模为2n-1。最后从规模是n的最优值找出最小值或最大值即可
1. 确定合适的数据结构
采用一维数组 a[i]来记录第i堆石子的数量;
sum[i]来记录前i堆石子的总数量;
二维数组Min[i][j]、Max[i][j]来记录第i堆到第j堆石合并的最小和最大花费
2. 初始化
输入石子的堆数n,然后依次输入各堆石子的数量存储在a[i]中,
令Min[i][j]=0,Max[i][j]=0,sum[0]=0,计算sum[i].
3.循环阶段
按照递归式计算2堆石子合并{ai, a i+1}的最小和最大花费
依次类推,直到求出所有堆的最小和最大花费
4.构造最优解
Min[1][n]和Max[1][n]是n堆石子合并的最小和最大花费。
如果还想知道具体的合并顺序,需要在求解的过程中记录最优决策,然后逆向构造最优解,
可以使用类似矩阵连乘的构造方法,用括号来表达合并的先后顺序。
从规模为n的最优值M[1][n],Min[2][n+1],Min[3][n+2],,,Min[n][2n-1]
中找最小值作为圆形石子合并的最小花费。
最大值同理
void straight(int a[], int n) {
for (int i = 1; i <= n; i++) {//初始化
Min[i][i] = 0, Max[i][i] = 0;
}
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {//枚举起点
int j = i + v - 1; //枚举终点j
Min[i][j] = INF; //初始化最大值
Max[i][j] = -1; //初始化最小值
int tmp = sum[j] - sum[i - 1]; //记录i...j之间的石子数之和
for (int k = i; k < j; k++) {
Min[i][j] = min(Min[i][j], Min[i][k] + Min[k + 1][j] + tmp);
Max[i][j] = max(Max[i][j], Max[i][k] + Max[k + 1][j] + tmp);
}
}
}
}
void Circular(int a[], int n) {
for (int i = 1; i <= n-1; i++) {
a[n + i] = a[i]; //扩大规模
}
n = 2 * n - 1;
straight(a, n); //以2n-1的规模进行直线的运算
n = (n + 1) / 2; //最后只需要规模为N的最优解
minn = Min[1][n];
maxx = Max[1][n];
for (int i = 2; i <= n; i++) {
if (Min[i][n + i - 1] < minn) {
minn = Min[i][n + i - 1];
}
if (Max[i][n + i - 1] > maxx) {
maxx = Max[i][n + i - 1];
}
}
}
#include
using namespace std;
const int INF = 100000;
const int N = 205;
int Min[N][N], Max[N][N];//求最小、最大值
int sum[N];//计算前i堆石子的数量总和,sum[0]=0,w(i,j)=sum[j]-sum[i-1]
int a[N];//记录各堆石子的数量
int minn, maxx;
void straight(int a[], int n) {
for (int i = 1; i <= n; i++) {//初始化
Min[i][i] = 0, Max[i][i] = 0;
}
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {
int j = i + v - 1;
Min[i][j] = INF;
Max[i][j] = -1;
int tmp = sum[j] - sum[i - 1];
for (int k = i; k < j; k++) {
Min[i][j] = min(Min[i][j], Min[i][k] + Min[k + 1][j] + tmp);
Max[i][j] = max(Max[i][j], Max[i][k] + Max[k + 1][j] + tmp);
}
}
}
}
void Circular(int a[], int n) {
for (int i = 1; i <= n-1; i++) {
a[n + i] = a[i];
}
n = 2 * n - 1;
straight(a, n);
n = (n + 1) / 2;
minn = Min[1][n];
maxx = Max[1][n];
for (int i = 2; i <= n; i++) {
if (Min[i][n + i - 1] < minn) {
minn = Min[i][n + i - 1];
}
if (Max[i][n + i - 1] > maxx) {
maxx = Max[i][n + i - 1];
}
}
}
int main()
{
int n;
cout << "请输入石子的堆数 n:";
cin >> n;
cout << "请依次输入各堆的石子数:";
for (int i = 1; i <= n; i++){
cin >> a[i];
}
straight(a, n);
cout << "路边玩法(直线型)最小花费为:" << Min[1][n] << endl;
cout << "路边玩法(直线型)最大花费为:" << Max[1][n] << endl;
Circular(a, n);
cout << "操场玩法(圆型)最小花费为:" << minn << endl;
cout << "操场玩法(圆型)最大花费为:" << maxx << endl;
return 0;
}
/*
请输入石子的堆数 n:6
请依次输入各堆的石子数:5 8 6 9 2 3
路边玩法(直线型)最小花费为:84
路边玩法(直线型)最大花费为:129
操场玩法(圆型)最小花费为:81
操场玩法(圆型)最大花费为:130
*/
#include
using namespace std;
const int INF = 100000;
const int N = 205;
int Min[N][N], Max[N][N], s[N][N];//求最小、最大值
int sum[N];//计算前i堆石子的数量总和,sum[0]=0,w(i,j)=sum[j]-sum[i-1]
int a[N];//记录各堆石子的数量
int minn, maxx;
void get_Min(int n) {
for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {
int j = i + v - 1;
int tmp = sum[j] - sum[i - 1];
int i1 = s[i][j - 1] > i ? s[i][j - 1] : i;
int j1 = s[i + 1][j] < j ? s[i + 1][j] : j;
Min[i][j] = Min[i][i1] + Min[i1 + 1][j];
s[i][j] = i1;
for (int k = i1 + 1; k <= j1; k++) {
if (Min[i][k] + Min[k + 1][j] < Min[i][j]) {
Min[i][j] = Min[i][k] + Min[k + 1][j];
s[i][j] = k;
}
}
Min[i][j] += tmp;
}
}
}
void get_Max(int n) {
for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {
int j = i + v - 1;
int tmp = sum[j] - sum[i - 1];
Max[i][j] = -1;
if (Max[i + 1][j] > Max[i][j - 1])
Max[i][j] = Max[i + 1][j] + tmp;
else
Max[i][j] = Max[i][j - 1] + tmp;
}
}
}
void straight(int a[], int n) {
for (int i = 1; i <= n; i++) {//初始化
Min[i][i] = 0, Max[i][i] = 0, s[i][i] = 0;
}
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
get_Min(n);
get_Max(n);
}
void Circular(int a[], int n) {
for (int i = 1; i <= n-1; i++) {
a[n + i] = a[i];
}
n = 2 * n - 1;
straight(a, n);
n = (n + 1) / 2;
minn = Min[1][n];
maxx = Max[1][n];
for (int i = 2; i <= n; i++) {
if (Min[i][n + i - 1] < minn) {
minn = Min[i][n + i - 1];
}
if (Max[i][n + i - 1] > maxx) {
maxx = Max[i][n + i - 1];
}
}
}
int main()
{
int n;
cout << "请输入石子的堆数 n:";
cin >> n;
cout << "请依次输入各堆的石子数:";
for (int i = 1; i <= n; i++){
cin >> a[i];
}
straight(a, n);
cout << "路边玩法(直线型)最小花费为:" << Min[1][n] << endl;
cout << "路边玩法(直线型)最大花费为:" << Max[1][n] << endl;
Circular(a, n);
cout << "操场玩法(圆型)最小花费为:" << minn << endl;
cout << "操场玩法(圆型)最大花费为:" << maxx << endl;
return 0;
}
这里用的是优化后的代码修改的,优化前的代码可能是时间复杂度的问题导致有两个测试点错误
#include
using namespace std;
const int INF = 100000;
const int N = 205;
int Min[N][N], Max[N][N], s[N][N];//求最小、最大值
int sum[N];//计算前i堆石子的数量总和,sum[0]=0,w(i,j)=sum[j]-sum[i-1]
int a[N];//记录各堆石子的数量
int minn, maxx;
void get_Min(int n) {
for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
for (int i = 1; i <= n - v + 1; i++) {
int j = i + v - 1;
int tmp = sum[j] - sum[i - 1];
int i1 = s[i][j - 1] > i ? s[i][j - 1] : i;
int j1 = s[i + 1][j] < j ? s[i + 1][j] : j;
Min[i][j] = Min[i][i1] + Min[i1 + 1][j];
s[i][j] = i1;
for (int k = i1 + 1; k <= j1; k++) {
if (Min[i][k] + Min[k + 1][j] < Min[i][j]) {
Min[i][j] = Min[i][k] + Min[k + 1][j];
s[i][j] = k;
}
}
Min[i][j] += tmp;
}
}
}
//void get_Max(int n) {
// for (int v = 2; v <= n; v++) {//枚举合并的堆数规模
// for (int i = 1; i <= n - v + 1; i++) {
// int j = i + v - 1;
// int tmp = sum[j] - sum[i - 1];
// Max[i][j] = -1;
// if (Max[i + 1][j] > Max[i][j - 1])
// Max[i][j] = Max[i + 1][j] + tmp;
// else
// Max[i][j] = Max[i][j - 1] + tmp;
//
// }
//
// }
//}
void straight(int a[], int n) {
for (int i = 1; i <= n; i++) {//初始化
Min[i][i] = 0, Max[i][i] = 0, s[i][i] = 0;
}
sum[0] = 0;
for (int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + a[i];
}
get_Min(n);
//get_Max(n);
}
//void Circular(int a[], int n) {
// for (int i = 1; i <= n-1; i++) {
// a[n + i] = a[i];
// }
// n = 2 * n - 1;
// straight(a, n);
// n = (n + 1) / 2;
// minn = Min[1][n];
// maxx = Max[1][n];
// for (int i = 2; i <= n; i++) {
// if (Min[i][n + i - 1] < minn) {
// minn = Min[i][n + i - 1];
// }
// if (Max[i][n + i - 1] > maxx) {
// maxx = Max[i][n + i - 1];
// }
//
// }
//
//}
int main()
{
int n;
//cout << "请输入石子的堆数 n:";
cin >> n;
//cout << "请依次输入各堆的石子数:";
for (int i = 1; i <= n; i++){
cin >> a[i];
}
straight(a, n);
cout << Min[1][n];
//cout << "路边玩法(直线型)最小花费为:" << Min[1][n] << endl;
//cout << "路边玩法(直线型)最大花费为:" << Max[1][n] << endl;
//Circular(a, n);
//cout << "操场玩法(圆型)最小花费为:" << minn << endl;
//cout << "操场玩法(圆型)最大花费为:" << maxx << endl;
return 0;
}