区间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 }