经典区间dp-石子合并
题目描述:
有n堆石子排成一排,每堆石子有一定的数量,将n堆石子合并成一堆。合并的规则是每次只能合并相邻的两堆石子,合并的花费为这两堆石子的总数。石子经过n-1次合并后成为一堆,求总的最小花费。
输入:有多组测试数据,输入到文件结束。每组测试数据的第1行有一个整数n,表示有n堆石子,n<250。接下来的一行有n个数,分别表示这n堆石子的数目。每堆石子至少一颗,最多10000颗。
输出:总的最小花费。
输入样例:
3
2 4 5
输出样例:
17
一看题目是不是认为贪心可以解决?
先sort然后合并最小的两堆。然后存值。然后再sort?
不,你错了。不tle才怪。
经典区间dp
dp[i][j]是指将第i堆到第j堆合并起来的最小代价。
sum[i]是指前缀和哈。第一堆到第i堆的石子总数
第i堆到第j堆的石子总数是sum[j] - sum[i - 1]
所以题目要求我们输出dp[1][n]就是答案~
初始化:
dp[i][i] = 0;
其余的初始化为INF
状态转移方程:
dp[i][j] = min(dp[i][k] + dp[k + 1][j]) + sum[j] - sum[i - 1];
k的范围:大于等于i小于j
代码部分:(代码部分没有处理文件结束这种)
#include <bits/stdc++.h>
#define mst(a, n) memset(a, n, sizeof(a))
using namespace std;
const int INF = 1 << 30;
const int N = 255;
int sum[N];
int dp[N][N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
scanf ("%d", &sum[i]);
sum[i] += sum[i - 1];
dp[i][i] = 0;
}
for (int len = 1; len < n; len++)
{
for (int i = 1; i <= n - len; i++)
{
int j = i + len;
dp[i][j] = INF;
for (int k = i; k < j; k++)
{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1]);
}
}
}
cout << dp[1][n] << endl;
return 0;
}
上面的代码时间复杂度:O(n^3)
处理n<250范围内的没问题。大于就不行了
所以处理dp常用的优化方法“平行四边形优化”
第三重循环是枚举区间的最优分割点。
这个地方可以优化。
我们可以从前面一次的优化点开始到后一次的优化点。这一些点中间进行枚举。
用s[i][j]表示区间i~j的最优分割点。
著名的以空间换时间
代码部分:
#include <bits/stdc++.h>
#define mst(a, n) memset(a, n, sizeof(a))
using namespace std;
const int INF = 1 << 30;
const int N = 255;
int sum[N];
int dp[N][N];
int s[N][N];
int n;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
scanf ("%d", &sum[i]);
sum[i] += sum[i - 1];
dp[i][i] = 0;
s[i][i] = i;
}
for (int len = 1; len < n; len++)
{
for (int i = 1; i <= n - len; i++)
{
int j = i + len;
dp[i][j] = INF;
for (int k = s[i][j - 1]; k <= s[i + 1][j]; k++)
{
if (dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1] < dp[i][j])
{
dp[i][j] = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i - 1];
s[i][j] = k;
}
}
}
}
cout << dp[1][n] << endl;
return 0;
}