石子合并问题

  先来看一种简单的石子合并:有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

  不管最小或者最大,我们会发现,当一堆石子一旦被合并,后面每次合并石子的重量(或者价值)都会被重复累加。所以我们用贪心的思想:当需要花费最小时,只要先合并最小的即可,而当需要花费最大时,只要先合并最大的即可,这样可以证明,最后得到的一定是最优解。而取最大/最小的操作可以用堆简单实现。

  然后就是复杂一些的石子合并:有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(或最大)。

  这个时候就不能用贪心的思想了,我们转而采用dp的思想。因为是对相邻的2堆石子合并,所以我们考虑对区间进行dp,定义f[i][j]为从第i堆石子到第j堆石子合并可以得到的最小(最大)值,那么当我们求f[i][j]的时候合并代价也就是∑(a[i]~a[j])是不变的(这里可以用差分来O(1)的求)。然而我们合并这个区间的的石子的时候是要将整个区间的石子先合并成两堆再进行合并,而这个操作的代价不是一定的,所以我们要使得到合并的两堆石子的代价尽量小。也就是要把i~j这个区间分成两段,将这两个区间内的石子进行合并,求出得到两堆石子代价最小(最大)值。那么我们就可以枚举中间点k来实现,于是我们得到状态转移方程f[i][j]=min/max(f[i][j],f[i][k]+f[k+1][j]+tem);(tem就是∑(a[i]~a[j])的值)。在dp之前还要先将f[i][i]先初始化为0。

  然而这里还有一个问题,那就是枚举k,i,j的顺序问题,因为从这个方程来看好像先枚举谁都会有没有求出的未知数。这里我们变换一下思路,我们首先枚举区间的长度,再枚举i,j可以直接由i+区间长度求出,然后在i,j之间枚举k就可以了。这样我们就能保证在求到f[i][j]的时候f[i][k]和f[k+1][j]就已经求出来了。因为求每个区间的时候,我们只要知道比这个区间小的区间的f[i][j]的值就可以了(后两个区间的长度比i~j要小),所以我们先枚举区间长度。这样这个问题就解决了。

  dp代码如下(求最小值):

  for(int len=1;len<n;len++)
        for(int i=1;i<=n-len;i++)
        {
            int j=i+len;
            f[i][j]=1000000000;
            int tem=sum[j]-sum[i-1];
            for(int k=i;k<j;k++)
                f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+tem);
        }

  这样整个程序的时间复杂度是O(n^3),还可以用四边形不等式进行优化(然而我不会,所以就先不写了)。

玲珑骰子安红豆,入骨相思知不知。
原文地址:https://www.cnblogs.com/hyl2000/p/5770148.html