『问题概述』
石子合并问题是经典的DP问题。首先它有如下3种题型:
1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成
分析:这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本质上是使用哈夫曼树算法。
2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(大)。
3)问题2的是在石子排列是直线情况下,现在改为环形排列求总花费最小(大)。
所以本次主要攻克的目标是问题2和3。
『线性相邻合并』
例题1. An old Stone Game(POJ 1738)
【题目大意】
有n堆石子(1<=n<=50000)排在一行,每堆石子给定一个重量。要把n堆石子合并成一堆,每次合并只能将相邻的两堆石子合并成一堆,代价为这两堆石子的重量和,求最小的总代价。
【输入】
输入包含几个测试用例。每个测试用例的第一行包含一个整数n,表示堆的数量。之后的n个整数描述了游戏开始时每堆的重量。 n = 0时结束。
【输出】
对于每个测试用例,在单行上输出答案。您可以假设答案不会超过1000000000(一亿)。
【输出样例】
1
100
3
3 4 3
4
1 1 1 1
0
【输出样例】
0
17
8
1.朴素的区间DP方法(O(n^3))
【分析】
题目要求的是一个最优解问题,很容易想到贪心和动态规划。但贪心有明显的错误,所以采用DP,而DP最重要的就是确定状态和找到状态转移方程。题目要求算出总体合并的最小代价,其子问题就是求出在合并的过程中每一步的最小代价。
状态:dp[i][j] - 表示从第i堆到第j堆的合并的最小代价。
那么状态如何转移呢?
通过尝试几个样例发现,每次都是相邻的两堆石子合并,而这两堆石子又是由另外的n堆石子合并而来的。那么我们可以得出一个结论,i到j堆石子最后会成为dp[i][k]和dp[k+1][j](i < k < j)这两堆石子。
所以从 i 到 j 的最小代价就是从 i 到 k 和从 k+1 到 j 的最小代价加上最后一次合并成一堆的代价—— i 到 j 的总石子重量。可以通过枚举k来得到最小代价。
最终的到状态转移方程 :dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);(i != j)。
如何枚举k也是本题的一个问题,因为没有确定 i 和 j 不好枚举 k ,所以采用的方法是枚举j - i -1。
『代码』
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
int dp[50010][50010];
int sum[50010];
int arr[50010];
int main(){
while(scanf("%d",&n) && n != 0){
sum[0] = 0;
for(int i = 1; i <= n; i++){
for(int j = i; j <= n; j++){
dp[i][j] = 2e9;
}
}
for(int i =1; i <= n; i++){
scanf("%d",&arr[i]);
sum[i] = sum[i-1]+arr[i];
dp[i][i] = 0;
}
if(n == 1){
printf("0\n");
}else{
for(int m = 1; m < n; m++){
for(int i = 1; i <= n; i++){
int j = i + m;
for(int k = i; k <= j; k++){
dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+