石子合并问题

类型1

有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。  

此类问题比较简单,就是哈夫曼编码的变形,用贪心算法即可求得最优解。即每次选两堆最少的,合并成新的一堆,直到只剩一堆为止。证明过程可以参考哈夫曼的证明过程。

huffman的开销就是:

cost of tree = Σ freq(i) * depth(i)

There is another way to write this cost function that is very helpful. Although we are only given frequencies for the leaves, we can define the frequency of any internal node to be the sum of the frequencies of its descendant leaves; this is, after all, the number of times the internal node is visited during encoding or decoding…The cost of a tree is the sum of the frequencies of all leaves and internal nodes, except the root. 

这样子的话,整个huffman的定义就和题中所说的一样了。

huffman建编码树,用最小堆是O(nlgn),还有用两个队列来建树的,只需要O(n)。

变形

有n堆石子,每堆1个,要合并成一堆,规定每次可以任意选两堆合并成新的一堆,两堆中较少的石子数记为该次合并的得分。

算法同样可以类比哈夫曼编码来证明。选最小的两个,然后再取最小插入到原数组中。

出自这里,也提到一种O(n)的算法,看不懂。

类型2

在一条直线上摆着N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为将的一堆石子的数量。设计一个算法,将这N堆石子合并成一堆的总花费最小(或最大)。

递推式为:

dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $i \le k < j$;  sum[i][j]是从第i堆(包含)到第j堆(包含)的总石子数。

求sum[i][j]可以通过O(n)预处理后在O(1)时间内获取。计算状态需要$n^2$,计算每一步最小值需要O(n),所以总共需要$O(n^3)$。

 1 int minStone(int arr[], int n) {
 2     if (n <= 0) return 0;
 3     vector<int> sum(n, 0);
 4     sum[0] = arr[0];
 5     for (int i = 1; i < n; ++i) {
 6         sum[i] = sum[i - 1] + arr[i];
 7     }
 8 
 9     vector<vector<int> > dp(n, vector<int>(n, INT_MAX));
10     for (int j = 0; j < n; ++j) { 
11         dp[j][j] = 0;
12         for (int i = j - 1; i >= 0; --i) {
13             for (int k = i; k < j; ++k) {
14                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i];
15                 if (v < dp[i][j]) dp[i][j] = v;
16             }
17         }
18     }
19     return dp[0][n - 1];
20 }

类型3

在一个圆形操场的四周摆放着n 堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2 堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。试设计一个算法,计算出将n堆石子合并成一堆的最小总得分。

环形数组的处理和上面类似,可以考虑的是复制一遍原数组,把复制的部分放在原数组末端。

这里最终要求的就是长度为n的最小值。dp的过程和前面类似,但是返回值就是长度为n的区间的最小值。

 1 int minStoneInCircle(int arr[], int n) {
 2     if (n <= 0) return 0;
 3     int newLen = n * 2 - 1; // special treatment for circular array
 4     vector<int> sum(newLen, 0);
 5     sum[0] = arr[0];
 6     for (int i = 1; i < newLen; ++i) {
 7         sum[i] = sum[i - 1] + arr[i % n];
 8     }
 9 
10     vector<vector<int> > dp(newLen, vector<int>(newLen, INT_MAX));
11     int min = INT_MAX;
12     for (int j = 0; j < newLen; ++j) { 
13         dp[j][j] = 0; 
14         for (int i = j - 1; i >= 0 && i >= j - n + 1; --i) {
15             for (int k = i; k < j; ++k) {
16                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i % n];
17                 if (v < dp[i][j]) dp[i][j] = v;
18             }
19         }
20         // only consider the subarray whose len is n 
21         if (j >= n - 1 && dp[j - n + 1][j] < min) min = dp[j - n + 1][j];
22     }
23     return min;
24 }

这里主要是在前面的代码基础上修改。实际上,像这种指定区间长度为n的dp,可以用区间长度作为状态,显得更自然些。复杂度同样是$O(n^3)$。

优化

网上查到的,利用四边形不等式,可以优化到$O(n^2)$。

当函数w[i,j]满足$w[i,j]+w[i',j'] \le w[i,j']+w[i',j],i \le i' \le j \le j'$时,称w满足四边形不等式。 就是交叉区间求和小于等于包含区间求和。

当函数w[i,j]满足$w[i’,j] \le w[i,j’], i \le i' \le j \le j'$时称w关于区间包含关系单调。就是被包含区间小于等于包含区间。

对于石子合并问题,递推式为dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $i \le k < j$;  

sum[i][j]就满足四边形不等式和区间包含关系单调。

从直觉上想,dp[i][j]是从子区间计算而来的,子区间满足四边形不等式和包含关系单调性,在子区间上求和再求最小值也同样满足四边形不等式和包含关系单调性。因为对于dp[i][j], dp[i+1][j],对于dp[i][j]的任何一个求值过程,dp[i+1][j]对应地总可以找到一个子区间包含它。

设search[i][j]为dp[i][j]中k的取值,也就是

search[i][j]=max{k | dp[i][j]=dp[i][k]+dp[k+1][j]+sum[i,j]};

证dp[i][j]满足四边形不等式就是为了证明search[i][j]符合区间包含单调性。

同样也是可以证明search[i][j]是区间单调的。所以

$search[i][j-1] \le search[i][j] \le search[i+1][j]$; 

所以可以将问题优化为:

dp[i][j]=min{dp[i][k]+dp[k+1][j]+sum[i][j]}, $search[i][j-1] \le k \le search[i+1][j]$; 

 

 1 int minStoneInCircle3(int arr[], int n) {
 2     if (n <= 0) return 0;
 3     int newLen = n * 2 - 1; // special treatment for circular array
 4     vector<int> sum(newLen, 0);
 5     sum[0] = arr[0];
 6     for (int i = 1; i < newLen; ++i) {
 7         sum[i] = sum[i - 1] + arr[i % n];
 8     }
 9 
10     vector<vector<int> > dp(newLen, vector<int>(newLen, INT_MAX));
11     vector<vector<int> > search(newLen, vector<int>(newLen, 0));
12 
13     int min = INT_MAX;
14     for (int j = 0; j < newLen; ++j) { 
15         dp[j][j] = 0; 
16         search[j][j] = j;
17         for (int i = j - 1; i >= 0 && i >= j - n + 1; --i) {
18             for (int k = search[i][j - 1]; k < j && k <= search[i + 1][j]; ++k) {
19                 int v = dp[i][k] + dp[k + 1][j] + sum[j] - sum[i] + arr[i % n];
20                 if (v < dp[i][j]) {
21                     search[i][j] = k;
22                     dp[i][j] = v;
23                 }
24             }
25         }
26         // only consider the subarray whose len is n 
27         if (j >= n - 1 && dp[j - n + 1][j] < min) min = dp[j - n + 1][j];
28     }
29     return min;
30 }

这个就是$O(n^2)$的了。

posted @ 2014-10-07 11:21  linyx  阅读(643)  评论(1编辑  收藏  举报