区间dp经典问题: 石子合并问题
问题描述:将n(1n200)堆石子绕圆形操场摆放,现要将石子有次序地合并成一堆.规定每次只能选相邻的两堆
石子合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分.(1)选择一种合并石子的方案,使得做
n-1次合并,得分的总和最小. (2)选择一种合并石子的方案,使得做n-1次合并,得分的总和最大

此题涉及到动态规划问题,即之前的操作会对后面的情况发生影响,所以无法根据当前状态进行贪心求得
正解,因此使用动态规划中的区间dp来进行求解

区间dp的解法思路:该题对应到区间dp中可理解为两个长度较小的区间信息向一个更长的区间信息发生了
转移,划分点k就是转移的决策,区间长度len就是dp的阶段,根据动态规划"选择最小的能覆盖状态空间的维
度集合"的思想,可以只用左,右端点表示DP的状态.

记录以下数据:
sum[i]:从第1--i堆石子的总数和
Fmax[i][j]:将从第i堆石子合并到第j堆石子的最大得分
Fmin[i][j]:将从第i堆石子合并到第j堆石子的最小得分
初始条件:Fmax[i][j]=0,Fmin[i][j]=INF;
状态转移方程:Fmax[i][j]=max(Fmax[i][k]+Fmax[k+1][j]+sum[j]-sum[i-1]);
Fmin[i][j]=min(Fmin[i][k]+Fmin[k+1][j]+sum[j]-sum[i-1]); (i<=k<=j)
对状态转移方程的个人理解:Fmax[i][j]中的得分分为两个方面:石子个数带来的基本得分和大于1次合并
所带来的额外得分,多次合并的额外得分则可以表示为Fmax[i][k]+Fmax[k+1][j],而石子总个数带来的基
础得分则为sum[j]-sum[i-1],而dp递推的过程中会转移到最初始的状态即i,j相邻,此时就无额外得分,且
这个状态的Fmax用来记录了额外得分,用于递推之后的得分.
时间复杂度为O(n^3)

特殊处理:破环成链
本题给定的石子是围成一个圈,而区间dp针对的区间应该是一条链,所以我们需要进行破环成链来进行处
理,将数据处理为能用区间dp处理的形式

处理方法:
法1:由于石子是一个圈,我们要将其变成一条链则需要断开其中一条边,因此若共有n条边,我们可以枚举
每一条边,共n次,将其做为断开的位置,每次处理都要遍历一次每条边,时间复杂度为O(n^2)
法2:将这条链拓展为原来两倍,拓展成2*n-1堆,其中第i堆与第n+i堆完全相同,这样只要对这2*n堆动态
规划后,枚举f(1,n),f(2,n+1),...,f(n,2*n-1)取最优值即可,时间复杂度为O(n)

代码实现: 

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N=205,INF=0x3f3f3f3f;
 4 int fi[N][N],fa[N][N];
 5 int a[N],sum[N];
 6 int n,minn,maxn;
 7 void dp() {//区间dp的操作方式  
 8     for (int l=2;l<=n;l++) {//枚举区间的所有长度  
 9         for (int i=1;i<=2*n-l+1;i++) {//枚举区间的起始左端点  
10             int j=i+l-1;//j为区间的右端点 
11             fi[i][j]=INF;fa[i][j]=0;
12             for (int k=i;k<j;k++) {//枚举左端点到右端点的所有断点  
13                 //以下部分计算多次合并带来的额外得分  
14                 fi[i][j]=min(fi[i][j],fi[i][k]+fi[k+1][j]);
15                 fa[i][j]=max(fa[i][j],fa[i][k]+fa[k+1][j]); 
16             } 
17             //以下部分计算石子个数带来的基本得分 
18             fi[i][j]+=sum[j]-sum[i-1];
19             fa[i][j]+=sum[j]-sum[i-1]; 
20         } 
21     }
22     minn=INF;maxn=0;
23     for (int i=1;i<=n;i++) {
24         minn=min(minn,fi[i][i+n-1]);
25         maxn=max(maxn,fa[i][i+n-1]);
26     }
27 } 
28 int main() {
29     scanf("%d",&n);
30     for (int i=1;i<=n;i++) {
31         scanf("%d",&a[i]);
32         //断环成链  
33         a[i+n]=a[i];
34     }
35     for (int i=1;i<=2*n;i++) {
36         sum[i]=sum[i-1]+a[i];
37         //可先预处理fi,fa从i到i的可得的分数一定为0  
38         fi[i][i]=0;
39         fa[i][i]=0;
40     }
41     dp();
42     printf("%d %d",minn,maxn);
43 }