原题:P4805 [CCC2016] 合并饭团 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Alphonse 有 N 个美味的饭团,它们大小不一,摆放成一行。他想把最大的饭团让给自己的基友。他可以执行以下操作:
-
如果两个相邻的饭团大小相同,Alphonse 可以把它们合并成一个新的饭团。新饭团的大小是两个原饭团大小之和。它将占据两个原饭团先前占据的位置。
-
如果两个饭团大小相同,且它们之间只有一个饭团,Alphonse 也可以把它们合并成一个新的饭团。(中间的饭团大小没有规定。)新饭团的大小是三个原饭团大小之和,并占据三个原饭团先前的位置。
分析
由于本题的“大问题”是求出大区间内的最优解,若划分成“小问题”,也就是要利用小区间内的最优解,推出大区间最优解。因此本题是一道区间dp题
抛开“摆成一圈”与“摆成一行”,本题与P1880 石子合并有着部分差别:
[1]石子合并方式只有本题的前一种操作。
[2]得分方式为“每次合并都得分”。
P1880
因此,P1880石子合并的递推公式较为简单:
其中:
dp[i][j]表示在区间[i,j]上的最大得分;
value[m]是第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];
}