poj 1738--石子合并 [GarsiaWachs算法]

石子合并算是一道比较经典的题目了,这里讨论一下朴素的动态规划和GarsiaWachs算法。

最简单的就区间动态规划了。设f(i, j)(i < j)为合并第i堆石子到第j堆石子的最小代价:

(需要枚举一个p)

以区间长度为阶段,时间复杂度为三次方的。一个对决策的优化,可以把转移优化为O(1)的。设g(i, j)为f(i, j)枚举的决策最优的p,那么显然有:
这样子枚举的p的范围就大大缩小了,p的取值范围是比较小的,只有一两个左右。然而,该算法的时间和空间复杂度都已经超出限制了。这时候GarsiaWachs算法就大显身手了。

假设有三堆石头,数量分别为a,b,c。那么应该如何合并才最好呢?若先合并a,b,那么花费为(a + b) + ((a + b) + c) = 2a + 2b + c,若先合并b,c,花费为(b + c) + ((b + c) + a) = a + 2b + 2c。通过比较,当a小于c时,第一种方案比较好,否则第二种较好。

GarsiaWachs算法的流程就是根据这个来的。

(转)http://fanhq666.blog.163.com/blog/static/81943426201062865551410/

设一个序列是A[0..n-1],每次寻找最小的一个满足A[k-1]<=A[k+1]的k,(方便起见设A[-1]和A[n]等于正无穷大)那么我们就把A[k]与A[k-1]合并,之后找最大的一个满足A[j]>A[k]+A[k-1]的j,把合并后的值A[k]+A[k-1]插入A[j]的后面。有定理保证,如此操作后问题的答案不会改变。

具体实现这个算法需要一点技巧,精髓在于不停快速寻找最小的k,即维护一个“2-递减序列”朴素的实现的时间复杂度是O(n*n),但可以用一个平衡树来优化(好熟悉的优化方法),使得最终复杂度为O(nlogn)。

从后往前依次插入每个数。用平衡树维护这个数列,每次插入的时候计算它以及它的前驱、后继是否违反了2-递减性质。查询从第一个比某个数大的数的时候可以用记录子树的最值的方法来实现。

2-递减性质是什么,如何用平衡树实现?不过如同大牛的博客所说“有好多数是相同的”,这个方法一般能过,但我还是想了解平衡树的做法,现在我没有这个能力也不应该在这里钻。这里做个了解,以后有能力再思考。

 
   

#include using namespace std; const int N = 50000; int d[N], n, m, ans; void Combine(int k) { int tmp = d[k] + d[k - 1]; ans += tmp; m --; for (int i = k; i < m; i ++) d[i] = d[i + 1]; int j = k - 1; for (; j > 0 && d[j - 1] < tmp; j --) d[j] = d[j - 1]; d[j] = tmp; while (j >= 2 && d[j] >= d[j - 2]) { tmp = m - j; Combine(j - 1); j = m - tmp; } } int main() { while (scanf("%d\n", &n)) { if (n == 0) return 0; for (int i = 0; i < n; i ++) scanf("%d\n", &d[i]); scanf("\n"); ans = m = 0; for (int i = 0; i < n; i ++) { d[m ++] = d[i]; while (m > 2 && d[m - 1] >= d[m - 3]) Combine(m - 2); } while (m > 1) Combine(m - 1); printf("%d\n", ans); } return 0; }

你可能感兴趣的:(动态规划)