算法提高 合并石子

来源:互联网 发布:js和javascript的区别 编辑:程序博客网 时间:2024/04/29 18:34
算法提高 合并石子  
时间限制:2.0s   内存限制:256.0MB
    
问题描述
  在一条直线上有n堆石子,每堆有一定的数量,每次可以将两堆相邻的石子合并,合并后放在两堆的中间位置,合并的费用为两堆石子的总数。求把所有石子合并成一堆的最小花费。
输入格式
  输入第一行包含一个整数n,表示石子的堆数。
  接下来一行,包含n个整数,按顺序给出每堆石子的大小 。
输出格式
  输出一个整数,表示合并的最小花费。
样例输入
5
1 2 3 4 5
样例输出
33
数据规模和约定
  1<=n<=1000, 每堆石子至少1颗,最多10000颗。
所有石头重量记录于a[i](0<=i<=n-1)
解释:sum[i]:前i+1个石头的总重量
   m[i][j]:第i下标至第j下标石头的最优合并方案(m[i][j]=min{m[i][k],m[k+1][j]}+sum[j]-sum[i-1])
其中k范围:[i,j-1]  (包括i和j-1的!)
s[i][j]:记录计算出m[i][j]最优时的k值.(根据四边形不等式而缩小k的取值范围)

原思路:1.从n个石头中选择相邻的i+1个石头合并(0<i<n),可知这样有n种选择方案,而最终我们需要的是选择相邻的n个石头合并的情况。
2.每一种选择情况可分为:n-i种不同起点的情况,例如:n=5,当i=0时,即从中选择相邻的(0+1)个石头合并
,它有5-0=5种不同起点的情况,分别为(0,0),(1,1)……(4,4),当i=1时……
3.特殊情况当i=0时,m[st][ed]=0;
在每一次选择完起点st情况之后,终点ed=st+i;为了把所有合并情况都考虑到,再进一步把2个石头,3个石头,4个石头……n个石头看成一堆石头合并问题转变为两堆石头合并的问题,
即m[st][ed]=m[st][k]+m[k+1][ed],其中k的取值范围[st,ed-1],因为问题是需要最少的代价去合并,所以需要m[st][ed]=min{m[st][k]+m[k+1][ed]},意思是:从st~ed-1下标的石头选择断开点k,这样就把一堆石头变为两堆石头,找出了(左边石头堆)最小花费和(右边石头堆)最小花费,即已花费了m[st][k]+m[k+1][ed]代价,再将这两堆石头合并,还需要sum[ed]-sum[st-1]代价,所以上面的m[st][ed]还需要加上一个将两堆已找好的石头合并代价值:(sum[ed]-sum[st-1])。
原始思路做出的代码是90%
根据四边形不等式而缩小了k的取值范围后就变为了100%
其中难点如何缩小k的取值范围:从[st,ed-1]变为[ s[st][ed-1],s[st+1][ed] ]
证明可在网上找出:百度搜索:四边形不等式
将i记为st,j记为ed.
设mk[i,j]=m[i,k]+m[k+1,j],s[i,j]=d;
对于任意k<=d,有:mk[i,j]>=md[i,j];这是因为当选择d为断点时,md[i,j]已经是最优(最小代价).
记s=s[i+1,j];那么可假设:当条件:(s<=d) 有: mk[i+1,j]>=md[i+1,j]成立,此时s<=d的所有情况已考虑完,其ms[i+1,j]最优(最小)也只能是md[i+1,j];
在其他没考虑的情况中可能有使:ms[i+1,j]<=md[i+1,j],可从s>=d的取值范围中获得更加小的ms[i+1,j],即s[i+1,j]>=s[i,j].
现在只需证明mk[i+1,j]>=md[i+1,j]是否成立即可.
根据网上搜索证明方法:
原式:(mk[i+1,j]-md[i+1,j])-(mk[i,j]-md[i,j])
=( m[i+1,k]+m[k+1,j]-( m[i+1,d]+m[d+1,j] ) ) -( m[i,k]+m[k+1,j] -( m[i,d]+m[d+1,j] ) )
( 左右相减消去了m[k+1,j]和m[d+1,j] )
=( m[i+1,k]+m[i,d] )-( m[i+1,d]+m[i,k] )①
因为合并石子问题符合四边形不等式:(i<i+1<k<d),m[i+1,k]+m[i,d]>=m[i,k]+m[i+1,d]
所以①>=0,原式>=0,可得mk[i+1,j]-md[i+1,j]>=mk[i,j]-md[i,j]
又因为对于任意k<=d,有:mk[i,j]>=md[i,j];即mk[i,j]-md[i,j]>=0
mk[i+1,j]-md[i+1,j]>=mk[i,j]-md[i,j]>=0
证明完毕.
以上是用先考虑k<=d的情况mk[i,j]>=md[i,j] 左下标变为i+1的时候k变为s,s<=d的情况已考虑……去证明出s[i+1,j]>=s[i,j].
第二个证明:s[i,j-1]<=s[i,j] 需要用先考虑k>=d的情况mk[i,j]>=md[i,j]……去证明出.
总结:四边形不等式(a<b<c<d),m[b,c]+m[a,d]>=m[a,c]+m[b,d]
   其中一堆相邻的数符合区间的单调性后并且在此范围又符合四边形不等式就可以判断出整堆数据都符合四边形不等式,利用四边形不等式去缩小类似合并石头的问题的边界取值。
上面合并石头的缩小k值的思路看似是先固定i+1,j的k取值范围,然后再固定i,j-1的k取值范围,实际是固定了s[i][j]的范围(i,j的k取值范围) 因为i+1,j和i,j-1的最优k值已在计算i,j最优k值之前算出了.
#include<iostream>#define INF 0x3f3f3f3fusing namespace std;int m[1001][1001];//m[i][j]=min{m[i][k]+m[k+1][j]}+sum[j]-sum[i-1];i为起点,j为终点int s[1001][1001];//s[i][j]保存当m[i][j]最少时的分界点k值int a[1001],sum[1001];int main(){int n,i,k;cin>>n;for(i=0;i<n;i++){cin>>a[i];if(i==0)sum[i]=a[i];elsesum[i]=sum[i-1]+a[i];}int st,ed;for(i=0;i<n;i++){for(st=0;st<n-i;st++){ed=st+i;m[st][ed]=INF;if(i==0){m[st][ed]=0;s[st][ed]=st;}else{for(k=s[st][ed-1];k<=s[st+1][ed];k++){if(m[st][ed]>m[st][k]+m[k+1][ed]){m[st][ed]=m[st][k]+m[k+1][ed];s[st][ed]=k;}}m[st][ed]=m[st][ed]+sum[ed]-sum[st-1];}}}cout<<m[0][n-1]<<endl;return 0;}

原创粉丝点击