这个问题备考到两次,分别是美团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;
}
}