区间DP模板:详解 线形 与 环形 石子合并

本蒟蒻通过这两个题入门区间DP,就分享一下心得,也算是复习一下了。
首先来看看题:

线形石子合并

N 堆石子排成一排,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 NN堆石子合并成 1 堆的最小得分。

#include<iostream>
using namespace std;
#define MAX 200
#define INF 0x3f3f3f3f //用这个表示无穷大
long long int f[MAX][MAX], sum[MAX], stone;
//f是dp数组,表示从第i个到j个的最优解
//sum是前缀和(本蒟蒻也是才知道还有这种操作
//stone是石头的值
int main()
{
	int n;
	cin >> n;
	
	for(int i = 1; i <= n; i ++)
	{
		cin >> stone;
		sum[i] = stone + sum[i - 1];
		//前缀和,用起来老方便了	
	}	
	
	for(int i = n - 1; i >= 1; i --)
	{
	//i代表的是每次讨论的长度,
	//我们要求从第1堆合并到第n堆的结果,还得分步来
	//比如要求1~5堆那么先讨论1~2,2~3,3~4,4~5,
	//再讨论1~3,2~4,3~5,然后再1~4,2~5,最后就有了1~5;
	//只不过这里是倒过来的
		for(int j = i + 1; j <= n; j ++) 
		{
		//j是终点,i是起点,表示从i合并到j的最优结果
			f[i][j] = INF;
			//求最小结果,就先初始为最大
			for(int k = i; k < j; k ++)
			{
			//精髓就是中间状态K,也就是分步讨论
			//讨论的过程--比如:对于f[1][5]的最优解,
			//可能是来自f[1][3]与f[4][5]合并,也可能是来自f[1][2]与f[3][5]合并,
			//所以我们需要通过这样一个判断找出它的最优解:
				if(f[i][j] > f[i][k] + f[k + 1][j] + (sum[j] - sum[i - 1]))
					f[i][j] = f[i][k] + f[k + 1][j] + (sum[j] - sum[i - 1]);	
			//由于每个f[i][k]和f[k+1][j]代表两堆石子,
			//那么要变成一堆,还得合并它们
			//也就是还得求从i到j的所有石子的值,即sum[j] - sum[i - 1];
			}			
		}	
	} 
	
	cout << f[1][n];
	return 0;
} 

也许我的描述会有一点问题,毕竟蒟蒻啊
但是大致上应该是这样的
现在,了解了上面这个题的思想,再来看看下面这个题:

环形石子合并

在一个圆形操场的四周摆放 N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。
试设计出一个算法,计算出将 N堆石子合并成 1堆的最小得分和最大得分。

输入格式
数据的第 1行是正整数 N,表示有 N堆石子。
第 2 行有 N 个整数,第 i个整数 ai表示第 i堆石子的个数。

输出格式
输出共 2行,第 1行为最小得分,第 2 行为最大得分。

输入

4
4 5 9 4

输出

43
54

传送门:https://www.luogu.com.cn/problem/P1880

这个题的不一样之处在于首尾可以相连,难点就在于如何去构建这样一个环形,
思前想后 发现问题很大 发现好像高中数学老师教给我们一个方法叫做 “化曲为直
果然数学基础决定上层建筑,蒟蒻
在这里插入图片描述

比如
环形示意
我们可以把它用一位数组存储:
stone[] ={1,2,3,4,5,1,2,3,4,5};

举个例子,首先1与2(stone[0]与stone[1])合并为3*(加 * 区别于开始就有的3),然后5在逻辑上与这个3 * 相临,
如果5与这个3 * 要合并的话,在此之前会有stone[5]与stone[6]的合并也是得到3*,
这样一来5与3 * 就在一维数组中相邻了。
有了这个操作,结合上一题的思想,代码实现也就容易了:

#include<iostream>
//#include<stdlib.h>
#include<string.h> 
#include<math.h>
using namespace std;

#define INF 0X3f3f3f3f

int sum[210], stone[210], dpmax[210][210], dpmin[210][210];

int main()
{
	int n;
	cin >> n;
	
	memset(dpmax, -INF, sizeof(dpmax));
	memset(dpmin, INF, sizeof(dpmin));
	
	//从1开始避免讨论混乱,也有利于第一个前缀和的处理
	for(int i = 1; i <= n; i ++)
	{
		cin >> stone[i];
		stone[i + n] = stone[i];
	}
	
	for(int i = 1; i <= 2 * n; i ++)
	{
	//前缀和,sum[0]本身就是0不需要特殊处理--从1开始的好处
		sum[i] += sum[i - 1] + stone[i];
	//代表自己与自己合并,当然不计入
	//其实可以省略这一步,因为全局的数组默认就是0
		dpmax[i][i] = 0;
		dpmin[i][i] = 0;
	}
	
	
	for(int i = 1; i < n; i ++)//每次讨论的长度(长度为i+1) 
	{
		for(int l = 1; l + i <= 2 * n; l ++)//左边界,右边界(l + i)不超过2n 
		{
			int r = l + i;//右边界 
			for(int k = l; k < r; k ++)
			{
				dpmax[l][r] = max(dpmax[l][r], dpmax[l][k] + dpmax[k + 1][r] + sum[r] - sum[l - 1]);
				dpmin[l][r] = min(dpmin[l][r], dpmin[l][k] + dpmin[k + 1][r] + sum[r] - sum[l - 1]);		
			}
		}
	}
	
	int MaxA = -INF, MinA = INF;
	
	for(int i = 1; i <= n; i ++)
	{
	//相当于考虑环形的起点是哪里,取最优的一种情况
		MaxA = max(MaxA, dpmax[i][i + n - 1]);
		MinA = min(MinA, dpmin[i][i + n - 1]);
	}
	
	cout << MinA << endl << MaxA << endl;
	
	return 0;
 } 

蒟蒻笔记,码字不易,如有错误,欢迎指正~~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

碳苯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值