经典区间dp-石子合并

经典区间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 
#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 
#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;
} 

你可能感兴趣的:(dp,动态规划,算法)