”知识是第一位的。“
(原题P1388)
我没有自己实现一遍这道题的完整情况,因为这题他是真的有bug,我们研究到8:30我才刚刚能看得懂题解到底说了啥(感谢学哥)。关键还是在于理解这种处理的思路。
首先正常来讲此题是一个分段DP,我们暂且不考虑0可能带来的影响。
根据(a+b)* c >= a+b*c <=> c >= 1,没有0的时候整个序列都是先把这串数合成(k+1)个段然后再相乘。说白了,能乘则乘。
那么思路就出来了:设前i个数用j个乘号得出的最大值为dp[i][j],每一个位置,我乘就是更新,不乘与原来的情况无异,那么显然有dp[i][j] = max(dp[i][j] , dp[k][j - 1] + sum[k] – sum[i])。
用这个递推式就完全足以应付不考虑0的情况了。我们只需要在sum数组里面存一遍前缀和就可以了。
注意:分段DP不是区间DP,没必要枚举把k个乘号一下子分给两边的情况,其实完全就是一般的DP,我们甚至可以说这玩意儿跟01背包就是一个原理。
接下来讨论一下0的问题。
0的问题,实质上就是打括号的问题。
经过了一大顿探讨,我们没有得出任何一个靠谱的特判,上一回hanoi那种两种模式并行的方式也不靠谱了,毕竟我们可以一会儿打括号一会儿不打括号。综上所述,我们只能像暴力一样考虑问题,设dp[i][j][k]表示从i到j分了k块得出的最大值, 依次枚举以下项目:括号数、区间长、左端点、分区间的位置、左区间用掉的括号数。
for(p=1;p<=m;p++) //枚举区间内的括号块的个数
for(r=p+1;r<=n;r++) //枚举区间的宽度
for(i=1;i+r-1<=n;i++){ //枚举左端点
j=i+r-1; //求出右端点
for(k=i;k<j;k++) //枚举区间dp中间点
for(q=0;q<k-i+1&&q<p&&p-q-1<j-k;q++){//枚举左区间括号块的个数
f[i][j][p]=max(f[i][j][p],f[i][k][q]*f[k+1][j][p-q-1]);
if(p-q<j-k)
f[i][j][p]=max(f[i][j][p],f[i][k][q]+f[k+1][j][p-q]);
}
}
(这段是从题解里面掏出来的。
题解地址https://www.luogu.com.cn/problem/solution/P1388)
这个和我们刚才写的那一种其实大同小异,i,j的处理压根没区别,就是这个括号数有所变化。这里我们认为,乘号必定消耗括号,毕竟这会使一个式子一分为二;这样一来,我们相当于模拟了所有可能取最大,完全去掉了不考虑0的时候部分“贪心“的处理。这是一个O(n5)的算法。