石子合并
首先看题:
有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; }