合并石子大总结
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了合并石子大总结相关的知识,希望对你有一定的参考价值。
合并石子大总结
石子合并问题是最经典的DP问题。首先它有如下3种题型:
一、非相邻两堆石子合并
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
分析:当然这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本问题实际上就是哈夫曼的变形。
二、直线型相邻两堆石子合并
有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。
有四种方法可以做这个:
(1)、
1、状态:
f[i][j]表示从第i堆合并到第j堆的最小代价
sum[i][j]表示第i堆石子到第j堆石子的总和
最终状态:
f[1][n]
初始状态:
f[i][i]=0
状态转移方程:
f[i][j] = min(f[i][k]+f[k+1][j]+sum[i][j])(i<=k<j)
f[i][j]初始设置为INT_MAX.
2、dp过程:
三层循环,最外层枚举一串合并的长度len,最小是2堆石子合并,最大是n堆.
然后枚举i,那么j就是i+len-1,在i和j之间枚举破点k,比较.
len…2->n
i…1->n-len+1 起点
k…i->j-1
3、其它
矩阵连乘与这类问题非常相似。矩阵连乘每次也是合并相邻两个矩阵(只是计算方式不同)。那么石子合并问题可用矩阵连乘的方法来解决。
那么最优子结构是什么呢?如果有N堆,第一次操作肯定是从n-1个对中选取一对进行合并,第二次从n-2对中选取一对进行合并,以此类推……
求出w的前缀和s,s[0]设为0,s[i]表示w[1]+w[2]+...+w[i],这样的话w[i]+w[i+1]+...+w[j]就是s[j]-s[i-1].
f[i][j]表示从第i堆合并到第j堆的最小代价,则f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])(i<=k<j),f[i][j]初始设置为
INT_MAX.
以长度为5的石子堆12345为例
下标 |
1 |
2 |
3 |
4 |
5 |
值 |
1 |
2 |
3 |
4 |
5 |
len为2 那么起点的最大值就是n-len+1=4
- for(int len=2;len<=n;len++)//归并的石子长度
- {
- for(i=1;i<=n-len+1;i++)//i为起点,j为终点
- {
- j=i+len-1; //由于len,表示有len个石子进行归并,从i开始,由于只有相邻的石子才能合并,所以结束位置j=i+len-1
- f[i][j]=INF; //初始值都置为无穷大
- for(k=i;k<=j-1;k++) //中间断开位置,查找在[i,j]什么位置设置断点k,f[i][j]取最优值
- {
- if(f[i][j]>f[i][k]+f[k+1][j]+sum[i][j])
- 10. f[i][j]=f[i][k]+f[k+1][j]+sum[i][j];
- 11. }
- 12. }
- 13. }
DP 过程:
阶段:以归并石子的长度为阶段,一共有n-1个阶段。
状态:每个阶段有多少堆石子要归并,当归并长度为2时,有n-1个状态;
当归并长度为3时,有n-2个状态;
当归并长度为n时,有1个状态。
决策:当归并长度为2时,有1个决策;当归并长度为3时,有2个决策;
当归并长度为n时,有n-1个决策。
(2)、
状态:
s[i][j]表示以i开始合并j个的最优值。
sum[i][j]表示第i堆石子到第j堆石子的总和
最终状态:
s[1][n]
初始状态
s[i][1]=0
状态转移方程:
以i开始的k个,结束下标为i+k-1
k表示从i开始合并的个数
f[i][j] = min(f[i][k]+f[i+k][j-k]+sum[i][i+j-1])(0<=k<j)
f[i][j]初始设置为INT_MAX.
以长度为5的石子堆12345为例
下标 |
1 |
2 |
3 |
4 |
5 |
值 |
1 |
2 |
3 |
4 |
5 |
例如i=1 j=5 k=2
f[1][5]= f[1][2]+ f[3][3]+ sum[1][5]
dp过程:
j…1->n
i…1->n-j+1
k…1->j
那么下面就是每一个阶段的具体合并
初始阶段就是s[1,1],s[2,1],s[3,1],s[4,1],s[5,1],因为一开始还没有合并,所以这些值应该全部为0。
第二阶段:两两合并过程如下,其中sum(i,j)表示从i开始数j个数的和
s[1,2]=s[1,1]+s[2,1]+sum(1,2)
s[2,2]=s[2,1]+s[3,1]+sum(2,2)
s[3,2]=s[3,1]+s[4,1]+sum(3,2)
s[4,2]=s[4,1]+s[5,1]+sum(4,2)
第三阶段:三三合并可以拆成两两合并,拆分方法有两种,前两个为一组或后两个为一组
s[1,3]=s[1,2]+s[3,1]+sum(1,3)或s[1,3]=s[1,1]+s[2,2]+sum(1,3),取其最优
s[2,3]=s[2,2]+s[4,1]+sum(2,3)或s[1,3]=s[2,1]+s[3,2]+sum(2,3),取其最优
.
.
.
第四阶段:四四合并的拆分方法用三种,同理求出三种分法的得分,取其最优即可。
以此类推
(3)、
状态:
dp[i][j]表示以i开始合并j次的最优值。
sum[i][j]表示第i堆石子到第j堆石子的总和
最终状态:
dp[1][n-1]
初始状态:
dp[i][0]
状态转移方程:
dp[i][j]=min(dp[i][k]+dp[i+k+1][j-k-1]+sum[i][j])(0<k<=j-1)
dp过程:
j…1->n-1
i…1->n-j
k…1->j-1
(4)、
状态:
f[i][j]表示从第i堆合并到第j堆的最小代价
s[i]表示前i堆石子的和
最终状态:
f[1][n]
初始状态:
f[i][i]=0
状态转移方程:
f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])(i<=k<j)
f[i][j]初始设置为INT_MAX.
dp过程:
i…n->1
j…i+1->n
k…i->j-1
参考代码:
1 #include <iostream> 2 #include <string.h> 3 #include <stdio.h> 4 5 using namespace std; 6 const int INF = 1 << 30; 7 const int N = 205; 8 9 int dp[N][N]; 10 int sum[N]; 11 int a[N]; 12 13 int getMinval(int a[],int n) 14 { 15 for(int i=0;i<n;i++) 16 dp[i][i] = 0; 17 for(int v=1;v<n;v++) 18 { 19 for(int i=0;i<n-v;i++) 20 { 21 int j = i + v; 22 dp[i][j] = INF; 23 int tmp = sum[j] - (i > 0 ? sum[i-1]:0); 24 for(int k=i;k<j;k++) 25 dp[i][j] = min(dp[i][j],dp[i][k]+dp[k+1][j] + tmp); 26 } 27 } 28 return dp[0][n-1]; 29 } 30 31 int main() 32 { 33 freopen("mergerStone2.txt","r",stdin); 34 int n; 35 while(scanf("%d",&n)!=EOF) 36 { 37 for(int i=0;i<n;i++) 38 scanf("%d",&a[i]); 39 sum[0] = a[0]; 40 for(int i=1;i<n;i++) 41 sum[i] = sum[i-1] + a[i]; 42 printf("%d\\n",getMinval(a,n)); 43 } 44 return 0; 45 }
三、环型相邻两堆石子合并
上面的四种直线型相邻两堆石子合并都可以修改少量代码后变成环型相邻两堆石子合并的解。
方法一:环化成两倍线长
方法二:%n
(1)、
因为圆形是首尾相接的,初一想,似乎与直线排列完全成了两个不同的问题。因为每次合并后我们都要考虑最后一个与第一个的合并关系。直线版的最优子结构不见了。f(i, j)表示i—>j合并的最优值似乎并不可行,因为我们可以得到的最优值第一步就是第一个与最后一个合并,那么f(i, j)并不能表示这种关系。
但是可以用两倍线性长的方法。
去看4吧。
(2)、
状态:
s[i][j]表示以i开始合并j个的最优值。
sum[i][j]表示第i堆石子到第j堆石子的总和
最终状态:
max(s[1][n])
初始状态
s[i][1]=0
状态转移方程:
以i开始的k个,结束下标为i+k-1
k表示从i开始合并的个数
f[i][j] = min(f[i][k]+f[(i+k)%n][j-k]+sum[i][i+j-1])(0<=k<j)
f[i][j]初始设置为INT_MAX.
以长度为5的石子堆12345为例
下标 |
1 |
2 |
3 |
4 |
5 |
值 |
1 |
2 |
3 |
4 |
5 |
例如i=1 j=5 k=2
f[1][5]= f[1][2]+ f[3][3]+ sum[1][5]
dp过程:
j…1->n
i…1->n
k…1->j
(3)、
状态:
dp[i][j]表示以i开始合并j次的最优值。
sum[i][j]表示第i堆石子到第j堆石子的总和
最终状态:
max(dp[i][n-1])
初始状态:
dp[i][0]=0
状态转移方程:
dp[i][j]=min(dp[i][k]+dp[(i+k+1)%n][j-k-1]+sum[i][j])(0<k<=j-1)
dp过程:
j…1->n-1
i…1->n
k…1->j-1
以长度为5的石子堆12345为例
下标 |
1 |
2 |
3 |
4 |
5 |
值 |
1 |
2 |
3 |
4 |
5 |
例如当j等于1(也就是合并一次,两个数),那么i的取值范围是(1,n-j+1)=(1,5)
就是保证有5 1这次合并
所以j为1时候的合并为(1,2),(2,3),(3,4),(4,5),(5,1)
注意和直线的时候的运行过程做对比。
(4)、
环形结构,经常采用双倍长度线性化手段,也就是说,把环形结构看成是长度为环的两倍的线性结构来处理。
环的长度是N,所以题目相当于有一排石子1....N+1....N,然后就可以用线性的石子合并问题的方法做了。
有个要注意的地方,f(i, j) 总是与 f(N +i, N +j) 相等的,所以可以减少一些不必要的计算。
此题的关键在于化环为线性结构,与N个数围成一圈,连续多少个数的最大和,异曲同工。
将N结构的线性表,转换为双倍长度的2N结构的线性表,然后在2N长度的表中,截取我们需要的长度为N的部分就可以了。
状态:
f[i][j]表示从第i堆合并到第j堆(合并成一堆的)的最小代价
s[i]表示前i堆石子的和
最终状态:
初始状态:
f[i][i]=0
状态转移方程:
f[i][j] = min(f[i][k]+f[k+1][j]+s[j]-s[i-1])(i<=k<j)
f[i][j]初始设置为INT_MAX.
dp过程:
len…1->n len表示归并的长度
i…1->2n i表示起点
那么j=i+len-1
k…i->j-1
参考代码:
1 #include <iostream> 2 #include <string.h> 3 #include <stdio.h> 4 5 using namespace std; 6 const int INF = 1 << 30; 7 const int N = 205; 8 9 int mins[N][N]; 10 int maxs[N][N]; 11 int sum[N],a[N]; 12 int minval,maxval; 13 int n; 14 15 int getsum(int i,int j) 16 { 17 if(i+j >= n) return getsum(i,n-i-1) + getsum(0,(i+j)%n); 18 else return sum[i+j] - (i>0 ? sum[i-1]:0); 19 } 20 21 void Work(int a[],int n) 22 { 23 for(int i=0;i<n;i++) 24 mins[i][0] = maxs[i][0] = 0; 25 for(int j=1;j<n;j++) 26 { 27 for(int i=0;i<n;i++) 28 { 29 mins[i][j] = INF; 30 maxs[i][j] = 0; 31 for(int k=0;k<j;k++) 32 { 33 mins[i][j] = min(mins[i][j],mins[i][k] + mins[(i+k+1)%n][j-k-1] + getsum(i,j)); 34 maxs[i][j] = max(maxs[i][j],maxs[i][k] + maxs[(i+k+1)%n][j-k-1] + getsum(i,j)); 35 } 36 } 37 } 38 minval = mins[0][n-1]; 39 maxval = maxs[0][n-1]; 40 for(int i=0;i<n;i++) 41 { 42 minval = min(minval,mins[i][n-1]); 43 maxval = max(maxval,maxs[i][n-1]); 44 } 45 } 46 47 int main() 48 { 49 freopen("mergerStone3.txt","r",stdin); 50 while(scanf("%d",&n)!=EOF) 51 { 52 for(int i=0;i<n;i++) 53 scanf("%d",&a[i]); 54 sum[0] = a[0]; 55 for(int i=1;i<n;i++) 56 sum[i] = sum[i-1] + a[i]; 57 Work(a,n); 58 //printf("%d %d\\n",minval,maxval); 59 printf("%d\\n",minval); 60 } 61 return 0; 62 }
以上是关于合并石子大总结的主要内容,如果未能解决你的问题,请参考以下文章