问题描述:
在一个圆形操场的四周摆放着n堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小得分和最大得分。
算法设计:
对于给定n堆石子,计算合并成一堆的最小得分和最大得分。
数据输入:
第一行是正整数n(1<=n<=100),表示有n堆石子。第2行有n个数,分别表示每堆石子的个数。
数据输出:
第1行的数是最小得分,第2行中的数是最大得分
输入样例:
4
4 4 5 9
输出样例:
43
54
设计思路:
本题就是选择不同位置的石子合并,可以在不同的位置分割,与矩阵合并的问题相似,假设在第k个位置选择分割,则左右两边都是最优分割方法,从而具有最优子问题的特征,可以用动态规划
dp[i][k] = Math.min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]),由于K的位置是不确定的,所以需要枚举k的位置,同时每次计算依赖的dp都是长度比其小的dp数组,如果枚举 i 和 j 递归中间会有很多地方重复计算,而用长度递增从下向上递推来求解dp就可以避免重复计算
同时这个问题要求石子按照圆圈排列,所以首尾两个石子是能够合并的,直接扩展数组,将两个一模一样的数组连接在一起,即环形转化为线性
4 4 5 9
转化为
4 4 5 9 4 4 5 9
代码:
import java.util.Scanner;
/**
* 石子合并问题--动态规划
*/
public class StoneMerge {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
//石子数量(从1到2*n)
int[] s = new int[2 * n + 1];
//合并第i到j堆的石子的得分
int[][] dpMin = new int[2 * n + 1][2 * n + 1];
int[][] dpMax = new int[2 * n + 1][2 * n + 1];
//前i堆石子的数量总和
int[] sum = new int[2 * n + 1];
for(int i = 1; i <= n; i++) {
s[i] = scanner.nextInt();
s[i + n] = s[i]; //扩展数组
}
for(int i = 1; i <= 2 * n; i++) {
sum[i] = sum[i - 1] + s[i]; //计算前缀和
dpMin[i][i] = 0; //初始化,只有一堆石子不用合并,没有得分
dpMax[i][i] = 0;
}
//动态规划
for(int len = 2; len <= n; len++) { //枚举长度
for(int i = 1; i <= 2 * n - len + 1; i++) { //枚举区间起点
int j = i + len - 1; //区间终点
dpMin[i][j] = Integer.MAX_VALUE;
dpMax[i][j] = Integer.MIN_VALUE;
for(int k = i; k < j; k++) { //枚举分割点
dpMin[i][j] = Math.min(dpMin[i][j], dpMin[i][k]+dpMin[k+1][j]+sum[j]-sum[i-1]);
dpMax[i][j] = Math.max(dpMax[i][j], dpMax[i][k]+dpMax[k+1][j]+sum[j]-sum[i-1]);
}
}
}
int resMin = Integer.MAX_VALUE;
int resMax = Integer.MIN_VALUE;
for (int i = 1; i <= n; i++) {
if(dpMin[i][i + n - 1] < resMin) {
resMin = dpMin[i][i + n - 1];
}
if(dpMax[i][i + n - 1] > resMax) {
resMax = dpMax[i][i + n - 1];
}
}
System.out.println(resMin);
System.out.println(resMax);
}
}
参考文章: