区间dp 石子合并&矩阵链乘法

石子合并

有n堆石子围成一个圈,每一堆石子可以和相邻的一堆合并得到新的一堆,所得的分数是新的一堆的数量。

所有的石子合并成一堆后,最大的分数,最小的分数是多少。

 

思路:

这是一个环,需要拆分成链,把这n堆,复制一遍,变成2n堆。

dp的时候还是得保证长度为n。dp_min[i][j]表示合并i~j这个区间所得到的最小的分数。

状态转移:dp_min[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + a[j] - a[i-1])

a数组是前缀和

#pragma warning(disable:4996)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn = 505;
const ll mod = 10000;
int a[205], dp_min[205][205], dp_max[205][205];
int main()
{
	int n, i, j, MAX, MIN;
	scanf("%d", &n);
	for (i = 1; i <= n; i++)
	{
		scanf("%d", &a[i]);
		a[i + n] = a[i];
		a[i] += a[i - 1];//这两句不能交换顺序,注意a[i]的值的含义
	}
	for (i = n+1; i <= 2*n; i++)
	{
		a[i] += a[i - 1];//后面的n个数的前缀和
	}
	for (int l = 2; l <= n; l++)//长度的取值最多为n
	{
		for (i = 1; (i + l - 1) < (n +n); i++)//j最大可以取到2n
		{
			j = i + l - 1;
			dp_min[i][j] = 0x3f3f3f;//设为最大的值
			dp_max[i][j] = -0x3f3f3f;//设为最小的值
			for (int k = i; k < j; k++)
			{
				dp_min[i][j] = min(dp_min[i][j], dp_min[i][k] + dp_min[k + 1][j] + a[j] - a[i - 1]);
				//状态转移,加上i~j这个区间的所有的数
				dp_max[i][j] = max(dp_max[i][j], dp_max[i][k] + dp_max[k + 1][j] + a[j] - a[i - 1]);
			}
		}
	}
	MAX = -0x3f3f3f;
	MIN = 0x3f3f3f;
	for (i = 1; i <= n; i++)
	{
		MAX = max(MAX, dp_max[i][i + n-1]);
		MIN = min(MIN, dp_min[i][i + n-1]);//保证是长度为n的区间
	}
	printf("%d\n%d\n", MIN, MAX);
	return 0;
}

矩阵链乘法

计算n个矩阵相乘变成1个矩阵,最少的运算次数。

比如一个n1 X m和一个 m X n2 的矩阵相乘,计算的次数就是n1 * m * n2

输入是n+1个数,第i个矩阵用第i个数和第i+1个数来表示维度,

思路:

区间dp,区间长度最小为2,最大为n。

dp[i][j]表示i~j这个区间内,全部乘起来,需要的最少的乘法运算次数。

状态转移:

dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + a[i-1]*a[k] * a[j])

#pragma warning(disable:4996)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<map>
#include<set>//没用的头文件太多,删去了一些,不然显得太长,
//无法编译的话,就再加上头文件吧
#define SITE set<node>::iterator 
using namespace std;
typedef long long ll;
const int inf = 1e9 + 10;
const int mod = 1000007;
const int maxn = 1e7;
ll n;
ll p[1005], m[1005][1005], s[1005][1005];
int main()
{
	ll i, j, k;
	cin >> n;
	for (i = 0; i <= n; i++)
	{
		cin >> p[i];//从0开始的n+1个数
	}
	for (ll l = 2; l <= n; l++)
	{
		for (i = 1; i <= n - l + 1; i++)
		{
			j = i + l - 1;
			m[i][j] = 0x3f3f3f3f3f3f;//设置为最大值
			s[i][j] = i;
			for (k = i; k < j; k++)
			{
				ll temp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                   //状态转移
				if (temp < m[i][j])
				{
					m[i][j] = temp;
					s[i][j] = k;//k是分割点
				}
			}
		}
	}
	cout << m[1][n] << endl;
	return 0;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值