2.4动态规划—石子合并问题

1. 问题描述

设有 N 堆石子排成一排,其编号为 1,2,3,…,N。每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2, 我们可以先合并 1、2 堆,代价为 4,得到 4 5 2, 又合并 1,2 堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4+9+11=24;如果第二步是先合并 2,3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4+7+11=22。
问题是:找出一种合理的方法,使总的代价最小,输出最小代价。

2. 代码实现

1. 问题分析

由于只能合并相邻的两堆石子,则说明最后一次合并一定是左边连续的一部分和右边连续的一部分进行合并
2.4动态规划—石子合并问题_第1张图片

2. 区间DP常用模板
for (int len = 2; len <= n; len++)           //区间长度
    for (int i = 1; i + len - 1 <= n; i++) { //枚举起点
        int j = i + len - 1;                 //区间终点
        for (int k = i; k < j; k++) {        //枚举分割点,构造状态转移方程
            dp[i][j] = max(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
        }
    }

3. 代码实现

(1) 按区间长度枚举

#include
#define N 310
int n;							//石子堆数
int s[N];						//s[j]表示前j堆石子的质量和
int f[N][N];					//状态函数

int main(){
	printf("请输入石子的堆数:");
	scanf_s("%d", &n);

	printf("请依次输入石堆的质量:");
	for(int i = 1; i <= n; i++){
		scanf_s("%d", &s[i]);
		s[i] += s[i - 1];
	}
	for(int len = 2; len <= n; len++)					//枚举区间长度(区间长度为1,则不用合并)
		for(int i = 1; i + len - 1 <= n; i++){			//枚举起点
			int j = i + len - 1;						//j表示区间终点
			f[i][j] = 1e8;
			for(int k = i; k < j; k++)					//枚举分割点,构造状态转移方程
				f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
		}
	printf("合并N堆石子的最小代价为:%d", f[1][n]);	
}

(2)逆序枚举,也可以保证每种状态都被提前计算

#include
#define N 310
int n;							//石子堆数
int s[N];						//s[j]表示前j堆石子的质量和
int f[N][N];					//状态函数

int main(){
	printf("请输入石子的堆数:");
	scanf_s("%d", &n);
	printf("请依次输入石堆的质量:");
	for(int i = 1; i <= n; i++){
		scanf_s("%d", &s[i]);
		s[i] += s[i - 1];
	}
	for(int i = n; i > 0; i--)					//
		for(int j = i; j <= n; j++){			//
			if(j == i){
				f[i][j] = 0;
				continue;
			}
			f[i][j] = 1e8;
			for(int k = i; k < j; k++)					//
				f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
		}
	printf("合并N堆石子的最小代价为:%d", f[1][n]);	
}

(3)记忆化搜索(时间会比递推慢一点,但思路简单)

#include 
#include 

using namespace std;

const int N = 307;

int a[N], s[N];
int f[N][N];

// 记忆化搜索:dp的记忆化递归实现
int dp(int i, int j) {
    if (i == j) return 0; // 判断边界
    int &v = f[i][j];

    if (v != -1) return v;

    v = 1e8;
    for (int k = i; k <= j - 1; k ++)
        v = min(v, dp(i, k) + dp(k + 1, j) + s[j] - s[i - 1]);

    return v;
}

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        s[i] += s[i - 1] + a[i];
    }

    memset(f, -1, sizeof f);
    cout << dp(1, n) << endl;
    return 0;
}

你可能感兴趣的:(算法设计与分析,动态规划,算法,c++)