P3146 [USACO16OPEN]248 题解
# 非常不错的一道区间DP题
如果您对区间DP还不是很了解,建议先尝试下“石子合并”这道差不多是模板的题,这里给出链接:石子合并
在您对了区间DP有了一定的了解之后,我们开始讲解这道题:
现在我们对这道题进行初步的分析:
1.此题为序列上的操作
2.只有相邻的两个数字相同才能进行合并,且合并后得到的数是由原数加上1后得到的
现在,我们在考虑这道为什么要用区间DP来解决:
1.最终的状态是由一个个较小的状态转移过来的
2.区间DP更容易判断可以进行合并的情况
综上所述,这道题的整体思路如下:
用f[i][j]表示将序列中的第i个数合并到第j个数全部合并所能得到的最大数值
状态转移方程如下:
if(f[i][pos] == f[pos+1][j])
{
f[i][j] = max(f[i][j], f[i][pos] + 1);
ans = max(ans, f[i][pos] + 1);
}
其中ans为最终答案,只有当左边的部分和右边的部分的数值相同时才能进行转移。
为什么当f[i][pos] !=f[pos+1][j]时,不能进行f[i][j]=max(f[i][pos],f[pos+1][j])的转移呢?
我们看f数组的定义,其中特别提到了“完全合并”
所以不能完全合并的话就不能进行转移
比如说,你有“7 5 7” 这么一个合并后的东西,显然这两个“7”无法合并,但如果你在转移时将“5”和“7”转移成了“7”,你的下一步中两个“7”就变的可以合并了,就会出错。
这也就解释了为什么f[1][n]并不一定是最终答案,因为从1到n并不一定能全部合并,即f[1][n]有可能是0,所以在下面的代码中的转移过程中就进行取最终答案的操作 (之前在此处少写了一句话,本次纠正)。
下面放代码:
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 255;
int n, val, ans;
int f[maxn][maxn];
inline int read(void) // 快读优化
{
int s = 0, w = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') w = -1;
for(; ch <= '9' && ch >= '0'; ch = getchar()) s= s * 10 + ch - '0';
return s * w;
}
int main()
{
n = read();
for(register int i = 1; i <= n; i++) val = read(), f[i][i] = val; // 预处理,当不进行合并时得到的价值就是自己
for(register int len = 2; len <= n; len++) // 枚举所合并区间的长度,因为转移是从小区间转移到大区间,所以要从小到大枚举区间的长度
{
for(register int i = 1; i <= n - len + 1; i++) // 枚举区间的左端点 ,当左端点等于 全长 减去 区间长度 再加一 的时候,右端点取到序列的最右边
{
int j = i + len - 1;
for(register int pos = i; pos < j; pos++) // 枚举当前区间可以由哪两个小区间转移过来
{
if(f[i][pos] == f[pos+1][j] && f[i][pos] != 0 && f[pos+1][j] != 0) // 如果这两个小区间合并出的值相等,就进行转移 ,特别地,如果两个的值都是0,表示这两个区间什么也合并不出来,所以不能进行转移
{
f[i][j] = max(f[i][j], f[i][pos] + 1);
ans = max(ans, f[i][pos] + 1); // 用ans来保存答案
//cout<< f[i][pos] << ' ' << f[pos+1][j] << '\n'; 调试用
}
}
}
}
cout << ans << '\n';
return 0;
}
讲解就到此结束了,有问题的话发评论或私信都可以。