一、讲解
1、作用
用于不知道从哪里合并的动态规划题,不比线性dp
2、解法步骤
即枚举区间长度,再枚举左端点,之后枚举区间的断点进行转移。
3、核心思路
既然让我求解在一个区间上的最优解,那么我把这个区间分割成一个个小区间,
求解每个小区间的最优解,再合并小区间得到大区间即可。所以在代码实现上,
我可以枚举区间长度len为每次分割成的小区间长度(由短到长不断合并),
内层枚举该长度下可以的起点,自然终点也就明了了。然后在这个起点终点之间枚举分割点,
求解这段小区间在某个分割点下的最优解。
4、图解法
二、石子合并直线型
1、代码
#include<stdio.h>
#include<string.h>
int min(int a,int b){
if(a>b)
return b;
else
return a;
}
int main(){
//dp[i][j]表示从第i堆石头合并到第j堆石头最小得分(从问题出发,缩小规模)
int dp[105][105];
//sum[i]表示前i堆石头的数量总和
int sum[105];
int n;
int i,j,k;
int l;
scanf("%d",&n);
sum[0]=0;
for(i=1;i<=n;i++){
scanf("%d",&j);
// 因为要求解区间和,先维护前缀后
sum[i]=sum[i-1]+j;
}
// 初始为0,dp[i][i]=0
memset(dp,0,sizeof(dp));
// 枚举区间长度,从两个区间开始
for(l=2;l<=n;l++){
// i=n-1+1,因为初始要l个,之后都是要一个一个的,算个数要+ ,枚举左端
for(i=1;i<=n-l+1;i++){
// 右端点,因为包含i,故减1
j=i+l-1;
// if(j>n)=
// break;
// 初始化最大值,
dp[i][j]=0x3f3f3f3f;
// 举区间的断点进行转移
for(k=i;k<j;k++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
}
// for(i=1;i<=n;i++){
// for(j=1;j<=n;j++)
// printf("%d ",dp[i][j]);
// printf("\n");
printf("%d",dp[1][n]);
}
2、sum与dp数组
sum是用来求两个区间合并所需要花费的钱,而dp是一个区间的已经花费的钱
三、能量项链环型
1、石子合并的sum是多变的,而这道题sum用head代替了,状态转移方程也变化了,主要看sum或者head,其他方面都没改变
2、环变成直线的方法
本题由于是环,还需破环为列,可以开两倍大的数组,即a[i]=a[i+n],便可在第n颗珠子时求到第1颗珠子的头标记(也即第n颗珠子的尾标记)
3、代码
#include<stdio.h>
#include<string.h>
int max(int x,int y){
if(x>y)
return x;
else
return y;
}
int main(){
int dp[205][205];
int head[105];
int n;
int i,j,k,l;
int ans=0;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&head[i]);
head[n+i]=head[i];
}
for(l=2;l<=n;l++){
//把环变成列,故有2n个
for(i=1;i<=n*2-l+1;i++){
j=i+l-1;
for(k=i;k<j;k++){
//head[i]*head[j+1]*head[k+1],例如,和合并第一个和第二个(1,2)(2,4),
//第一个的值为1,第二个的值为2,第三个的值为4,i=1,k=1,j=2,故用head[i]*head[j+1]*head[k+1]
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+head[i]*head[j+1]*head[k+1]);
}
}
}
for(i=1;i<=n;i++){
ans=max(ans,dp[i][i+n-1]);
}
printf("%d",ans);
}
参考资料
能量项链 (区间DP)