石子合并

来源:互联网 发布:数据封装和拆封过程 编辑:程序博客网 时间:2024/04/29 23:42

首先看题:

有n堆石子排成一列,每堆石子有一个重量w[i], 每次合并可以合并相邻的两堆石子,一次合并的代价为两堆石子的重量和w[i]+w[i+1]。问安排怎样的合并顺序,能够使得总合并代价达到最小。

设状态f(i,j),1<=i<=j<=n表示从第i堆到第j堆所需要的最小代价。

找规律 a[i,j]表示i到j堆的数量

f(1,1)=w[1]

f(1,2)=w[1]+w[2]=f(1,1)+f(2,2)       //没有+a[1,2],因为这只是合并了2->1个堆,自己画图

f(2,2)=w[2]

f(1,3)=min{f(1,1)+f(2,3),f(1,2)+f(3,3), f(1,3)+f(3,3)}+a[1,3] //想想,为什么要加上。画图

f(2,3)=w[2]+w[3]=f(2,2)+f(3,3)

f(3,3)=w[3]

f(1,4)=min{f(1,1)+f(2,4), f(1,2)+f(3,4), f(1,3)+f(3,4),f(1,4)+f(4,4)}

.......

统一一下f(i,i)即将它初始化为0,不然值就会多加了一倍TAT。然后可以发现:

f(i,j)=min{f(i,j), f(i,k)+f(k+1,j)}+a[i,j]  (i<=k<=j-1, 1<=i<=j<=n)

a[i,j]可以用dp在O(n)的时间内得到,设一个一维数组

sum[i]=sum[i-1]+w[i] (2<=i<=n)

sum[1]=a[1]

a[i,j]就相当于sum[j]-sum[i-1]

这时候方程就是

f(i,j)=min{f(i,j), f(i,k)+f(k+1,j)}+sum[j]-sum[i-1](,i<=k<=j-1, 1<=i<=j<=n)

咱们来画图。。(矩阵)

用一个二维数组F[i][j]表示f(i,j)。在矩阵上行的表示i,列表是j(为了方便)

如图:

由此可知道顺序。。则递推程序如下。(我不要下标0了,从1开始,方便,而且-1不会越界)

for(j = 1; j <= n; j++)for(i = j; i > 0; i--)for(k = i; k < j; k++)f[i][j] = min(f[i][j], f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);

j可以看成图中的i, i则是j。f[i][j]即从i堆到j堆所需最小的代价。

初始化很重要= =。 非常,非常= =。。。。要将f[i][i]初始化为0,其中1<=i<=n

并且将f[i][j]都要初始化为INF(一个很大很大的值),否则会出错。

因为是从下标1开始,下标0的自然要初始化为0,如果声明的是全局变量一般都会清0的,这一步可以省略。

放上完整代码= =。

#include <iostream>#include <algorithm>using namespace std;const int MAXN = 120;const int INF = 1000000000;int w[MAXN], n, f[MAXN][MAXN], sum[MAXN];int i, j, k;int main(){cin >> n;for(i = 1; i <= n; i++) for(j = 1; j <= n; j++) f[i][j] = INF;for(i = 1; i <= n; i++) {cin >> w[i]; f[i][i] = 0; sum[i] += sum[i-1]+w[i];}for(j = 1; j <= n; j++)        for(i = j; i > 0; i--)        for(k = i; k < j; k++)        f[i][j] = min(f[i][j], f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);cout << f[1][n] << endl;return 0;}