首先思考一下,如果不是环,而是链,该怎么做?
1、找每一子情况之间的关系,两堆小石堆合成大石堆,dp[大]=dp[小]+dp[小]
2、明显区间dp,设计dp状态,设dp[i][j]表示第 i 堆到第 j 堆这一段连续石子合并为一堆的最小代价
3、在区间[i,j]中,最后一次合并一定是左右两大块合并。枚举中间分割点k(i ≤ k < j),转移式:dp[i][j] = min( dp[i][k] + dp[k+1][j] + sum(i,j) )。
但是重点来了,我们发现他是环————
我们可以将数组扩成 2n (如本来是1,2,3将其扩充为1,2,3,1,2,3)将它依次破坏成所有长度等于 n 的连续区间[i, i+n-1],就等价于在环的不同位置剪开得到的线性链。
为什么可以这样说??
原来有n 组相邻边,我们合并只需要合并n-1次,那就肯定有一次相邻是没用到的,所以我们就可以从这个缺口剪开,使他变成链。
接下来回到代码,也有一个重点,for循环循环的不是i和j而是len和i,因为每次转移他包含的小区间应该先处理!!
最后时间复杂度为(n的三次方),但数据小不会炸。
奉上代码,小学生码风助于理解:
#include<bits/stdc++.h>
using namespace std;
long long a[305],f[305],dp[305][305],dp1[305][305];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<2n;i++)
{
f[i]=f[i-1]+a[i];
}
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=2n;l++){
int r=len+l-1;
dp[l][r]=1e18;
dp1[l][r]=-1e18;
for(int m=l;m<r;m++){
dp[l][r]=min(dp[l][r],dp[l][m]+dp[m+1][r]+f[r]-f[l-1]);
dp1[l][r]=max(dp1[l][r],dp1[l][m]+dp1[m+1][r]+f[r]-f[l-1]);
}
}
}
long long ansmin=1e18,ansmax=-1e18;
for(int i=1;i<=n;i++){
ansmin=min(dp[i][i+n-1],ansmin);
ansmax=max(dp1[i][i+n-1],ansmax);
}