P4805合并饭团 + P1880合并石子

文章讲述了如何解决P4805问题,即在一个包含多个饭团的序列中,通过合并相同大小的饭团来最大化最终的最大饭团重量。作者介绍了区间DP方法,强调了合并操作必须满足连续饭团大小相同且构成一个整体的条件,以及递推公式和复杂度分析。
摘要由CSDN通过智能技术生成

原题:P4805 [CCC2016] 合并饭团 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Alphonse 有 N 个美味的饭团,它们大小不一,摆放成一行。他想把最大的饭团让给自己的基友。他可以执行以下操作:

  • 如果两个相邻的饭团大小相同,Alphonse 可以把它们合并成一个新的饭团。新饭团的大小是两个原饭团大小之和。它将占据两个原饭团先前占据的位置。

  • 如果两个饭团大小相同,且它们之间只有一个饭团,Alphonse 也可以把它们合并成一个新的饭团。(中间的饭团大小没有规定。)新饭团的大小是三个原饭团大小之和,并占据三个原饭团先前的位置。

分析

       由于本题的“大问题”是求出大区间内的最优解,若划分成“小问题”,也就是要利用小区间内的最优解,推出大区间最优解。因此本题是一道区间dp题

        抛开“摆成一圈”与“摆成一行”,本题与P1880 石子合并有着部分差别:

                [1]石子合并方式只有本题的前一种操作。

                [2]得分方式为“每次合并都得分”。

P1880

        因此,P1880石子合并的递推公式较为简单:

                dp[i][j] = max ( dp[i][k] + dp[k+1][j] + \sum_{m=i}^{j}value[m] )

        其中:
                dp[i][j]表示在区间[i,j]上的最大得分;
                value[m]是第m堆石子的数量
                \sum_{m=i}^{j}value[m]是从[i,j]区间内所有石子的数量总和,也就是“本次合并的得分”。

        只要将dp[i][i]初始化为0(一堆石子没法与自身合并,无法得分),就可以用这个公式解题。

P4805

        至于本题P4805,本题特点为:

                [1]最终得分数==最大饭团重量

                [2]必须要两个饭团大小相同,才能二合一/三合一。

因此,我解决本题所用的方式较为复杂:

        1. 先对区间长度len做for循环,依次处理长度为1、2、……、n-1的区间

        2. 在判断能否二合一时,不仅要看当前方式二合一后的dp[i][i+len]大小是否比别的方式更大,还要判断当前方式的二合一所用到的dp[i][k]和dp[k+1][i+len]是否为一个整体。

        举例说明:有四个饭团 3 1 2 3,虽然dp[0][1]==dp[2][3]==3,但是这两个大小为3的饭团实际上无法合成,因为区间[0,1]和区间[2,3]都不是一个“整体”,[0,1]中除3以外还包含了1这个饭团,[2,3]中除3以外还包含了2这个饭团。所以此时也不能进行二合一。

        同理,判断能否三合一时,要判断三个区间[i,k],[k+1,j],[j+1,i+len]是否都为整体。

        3. 在确认能合并时,大区间的得分包含了左边部分小区间的目前得分、右边部分小区间的目前得分(三合一时还包括中间部分)

综上,我的解题代码为:

#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
#define max_n 410

int n;
int dp[max_n][max_n] = { 0 };//dp[i][j]存放了区间[i,j]的最高分
bool isInteger[max_n][max_n] = { 0 };//isInteger[i][j]存放了区间[i,j]是否能合成一整个饭团

void read()
{
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> dp[i][i];
		isInteger[i][i] = true;//一个饭团肯定是一个整体
	}
}

void solve()
{
	for (int len = 1; len < n; len++) 
		for (int i = n-len-1; i >=0; i--)//i < i+len < n
		{
            //res记录最高分,最后赋值给dp[i][i+len]
			int res = max(dp[i][i + len - 1], dp[i + 1][i + len]);
			for (int k = i; k < i+len; k++)//第一个指针k. i <= k < i+len
			{
                //二合一
				if ( (dp[i][k] == dp[k + 1][i + len] )  && (isInteger[i][k]) && (isInteger[k + 1][i + len])&& (2 * dp[i][k] >= res))
				{
					res = 2 * dp[i][k];
					isInteger[i][i + len] = true;//标记:dp[i][i+len]是合成得到的,是个整体
				}

				for (int l = k + 2; l <= i + len; l++)//第二个指针l. k+1 <= l <=i+len
					if (dp[i][k] == dp[l][i + len] && (isInteger[i][k]) && (isInteger[l][i + len]) && (isInteger[k + 1][l - 1]) && (2 * dp[i][k] + dp[k + 1][l - 1] >= res))
					{
                        //三合一
						res = 2 * dp[i][k] + dp[k + 1][l - 1];
						isInteger[i][i + len] = true;
					}
			}
			dp[i][i + len] = res;
		}
}

int main()
{
	read();
	solve();
	cout << dp[0][n - 1];
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值