石子的合并问题

这个问题备考到两次,分别是美团2020春招和腾讯2020秋招。以下为基于DP的题解…转载自他人。

1. 线性(相邻)合并问题

题目描述:
一条直线上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

请编辑计算出将n堆石子合并成一堆的最小得分。
Input

输入有多组测试数据。

每组第一行为n(n<=100),表示有n堆石子,。

二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量ai(0<ai<=100)

Output

每组测试数据输出有一行。输出将n堆石子合并成一堆的最小得分。

Sample Input

3

1 2 3

Sample Output

9 

简要概括

两堆石子的得分,就是两堆石子相加的值
注意,这里只能是相邻的两堆石子。加起来的可以认为是一个新的大的石子。
如果要是挑出任意两个就类似与哈夫曼树,但是这里只能我们去尝试着,找出最小。
算法思想
我们利用动态规划,记录每一个状态的值,逐渐找出完整的状态。
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:

算法思想

我们利用动态规划,记录每一个状态的值,逐渐找出完整的状态。
设dp[i][j]表示第i到第j堆石子合并的最优值,sum[i][j]表示第i到第j堆石子的总数量。那么就有状态转移公式:

if( i == j)
dp[i][j] = 0;
if( i != j)    //这里我们要借助另一个变量k
{
	dp[i][j] = dp[i][k] +dp[k+1][j] + sum[i][j];       (k >= i && k < j) k不能等于 j。  因为dp[k+1]的越界。
}

其中sum[i][j]的含义是 :第i堆到第j堆的质量和。
初始时,sum[1][1] … sum[i][i] = v[i] 。和别人合并时,比如第一堆和第二堆进行合并 sum[1][2] = sum[1][1] +sum[2][2] = v[1] + v[2]

我们维护一个len变量,代表区间长度。从【i,i + len】算出每一个最优的小区间合并石子的值。 比如石子有4个,我们可以先求出
区间为1的,【1,2】,【2,3】,【3,4】的最优小区间的合并得分。
区间为2的,【1,3】,【2,4】,(但是dp[1][3] 需要用到dp[1][2]或者dp[2][3]的值,这就是我们为什么新建一个k变量的原因)。
k变量表示的从区间起点i到区间终点,在中间怎么样分才可以使dp[i][j]可以分到最小值。

比如:
len = 1时,i = 1时, j = i+ len = 2。 此时sum[i][j]为i到j的和,k = 1时,就sum[1][2] = sum[1][1] + sum[2][2],同时算出子结构dp[1][2]。这样下次还可以用这个dp[1][2] 作为已知变量,求出 后面的 dp[i][j] 。

#include <iostream>
using namespace std;

int main()
{
	int n = 0;
	while (cin >> n)
	{
		vector<int> v(n + 1);
		vector<vector<int>> dp(n +1 , vector<int>(n +1, INT_MAX));
		vector<vector<int>> sum(n + 1, vector<int>(n + 1, 0));
		for (int i = 1; i <= n; i++)
		{
			cin >> v[i];
		}
		for (int i = 1; i <= n; i++)
		{
			sum[i][i] = v[i];
			dp[i][i] = 0;
		}
		for (int len = 1; len < n; len++)  // 区间长度
		{
			for (int i = 1; i + len <= n; i++)  //区间起点
			{
				int j = i + len; //区间终点
				for (int k = i; k < j; k++)
				{
					sum[i][j] = sum[i][k] + sum[k + 1][j];
					dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[i][j]);
				}
			}
		}
		cout << dp[1][n] << endl;

	}
}
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页