关于一个小问题的联想

来源:互联网 发布:linux mysqldump命令 编辑:程序博客网 时间:2024/04/28 18:48

潮湿的天气和人的心理一样,散发出懒洋洋的热空气。懒的写东西,帖一篇Love_Shsean的文。

我们设柱型图的元素高度为h [ ],那么我们要求的就是max { h [i] * (Right [i] - Left [i] + 1) },其中Left [i]分别是以h [i]为中心,向左拓展使得h [j]~ h [i] <= h [i]的最大j,Right [i]也有类似的定义。查看更多精彩图片

由此,我们来看一个子问题:已知数组h, 如何求Left [ ]。
算法1、枚举,O (N ^ 2)。
算法2、线段树,O (N Log N)。
算法3、用已经计算出的结果来加速当前的计算。
显然有这样的结论,如果当前计算的i (i > 0),h [i] > h [i - 1],显然Left [i] = i,否则我们要比较h [i - 2] 和 h [i],那么如果我们已知了Left [ i - 1],能不能用Left [i - 1]的结果来省去一些不必要的比较呢?
h [i] <= h [i - 1], h [i - 1] <= h [ Left [ i - 1] ] ,于是我们只要从Left [i - 1] - 1开始和h [i]比较就行了,省去了中间不必要的比较,同样的道理,我们可以一直迭代的来比较。代码只有两行:
for (i = 0; i < N; i ++)
   for (Left [i] = i; Left [i] && h [Left [i] - 1] >= h [i]; Left [i] = Left [Left [i] - 1]);
极为简洁,O (N * Log*N) 已经很接近于O (N)了。
算法4、
在看算法4之前,我们先来看Zoj 1985的官方解法。

Linear search using a stack of incomplete subproblems
We process the elements in left-to-right order and maintain a stack of information about started but yet unfinished subhistograms. Whenever a new element arrives it is subjected to the following rules. If the stack is empty we open a new subproblem by pushing the element onto the stack. Otherwise we compare it to the element on top of the stack. If the new one is greater we again push it. If the new one is equal we skip it. In all these cases, we continue with the next new element.
If the new one is less, we finish the topmost subproblem by updating the maximum area w.r.t. the element at the top of the stack. Then, we discard the element at the top, and repeat the procedure keeping the current new element. This way, all subproblems are finished until the stack becomes empty, or its top element is less than or equal to the new element, leading to the actions described above. If all elements have been processed, and the stack is not yet empty, we finish the remaining subproblems by updating the maximum area w.r.t. to the elements at the top.
For the update w.r.t. an element, we find the largest rectangle that includes that element. Observe that an update of the maximum area is carried out for all elements except for those skipped. If an element is skipped, however, it has the same largest rectangle as the element on top of the stack at that time that will be updated later.
The height of the largest rectangle is, of course, the value of the element. At the time of the update, we know how far the largest rectangle extends to the right of the element, because then, for the first time, a new element with smaller height arrived. The information, how far the largest rectangle extends to the left of the element, is available if we store it on the stack, too.
We therefore revise the procedure described above. If a new element is pushed immediately, either because the stack is empty or it is greater than the top element of the stack, the largest rectangle containing it extends to the left no farther than the current element. If it is pushed after several elements have been popped off the stack, because it is less than these elements, the largest rectangle containing it extends to the left as far as that of the most recently popped element.
Every element is pushed and popped at most once and in every step of the procedure at least one element is pushed or popped. Since the amount of work for the decisions and the update is constant, the complexity of the algorithm is O(n) by amortized analysis.

这个解法维护了一个栈,递归地以O (N)的时间计算出了结果。
看一下代码吧:

// Problem   Histogram// Algorithm Recursive// Runtime   O(n)// Author    Walter Guttmann// Date      12.01.2003#include <algorithm>#include <cassert>#include <fstream>#include <iostream>using namespace std;ifstream in("histogram.in");double h[1048576], max_area;int n;// Let r' be the right-most rectangle such that for all r<=i<=r': low<=h[i].// Then a call calc_max_area(r, left, low) updates max_area by considering// the largest rectangles in the histogram in the intervals// (1) [i..j] where r<=i<=j<=r', and// (2) [left..j] where r<=j<=r',// assuming that for all left<=i<=r: h[r]<=h[i], i.e., we can extend any// rectangle in the interval [r..j] to the interval [left..j] for r<=j<=r'// without lowering its height. The return value is 1+r'.int calc_max_area(int r, int left, double low){  assert(left <= r);  assert(low <= h[r]);  int next = r+1;  if (next < n && h[r] < h[next])    next = calc_max_area(next, next, h[r]+1);  assert(next == n || h[next] <= h[r]);  max_area = max(max_area, (next-left)*h[r]);  return (next == n || h[next] < low) ? next : calc_max_area(next, left, low);}int main(){  cout.setf(ios::fixed);  cout.precision(0);  while (in >> n)  {    if (n == 0) break;    assert(1 <= n && n <= 100000);    for (int i=0 ; i<n ; i++)      in >> h[i];    max_area = 0;    assert(calc_max_area(0, 0, 0) == n);    cout << max_area << endl;  }  return 0;}我们研究calc_max_area这个函数,他计算的是以r为中心,向两边拓展后得到的最大矩形,
其中left = left [i], next = right [i] + 1,递归的过程中维护一个low值,最后
返回以low为下界的next值。
int calc_max_area(int r, int left, double low){ int next = r+1; if (next < n && h[r] < h[next]) next = calc_max_area(next, next, h[r]+1);
// 注意h [r] < h [next]中的"<", 这里跳过了"="的情况,
// 因为在计算相等的一段的最后一个元素,会包括所有的情况,
// 在特殊的情况下要改成"<="
// 这里next = right [r] + 1 max_area = max(max_area, (next-left)*h[r]);
// 返回以low为下界,从r能拓展出的最远 return (next == n || h[next] < low) ? next : calc_max_area(next, left, low);}到这里已经没什么好说的了,Left [] 和 Right [] 都在O (N)的时间内得以计算,只要
稍微加一些代码就可以了。^_^
相同的解法在IOI06朱晨光大牛的论文中有提到,说的就是后面的Zoj2180,但用的是本解法。
举几个类似的例子供大家参考:
Zoj 1985, Zoj 2180, Zoj 2422, Zoj 2642
其中要特殊说的是Zoj 2180,求得是最大非禁字图,就是一个N * M的矩阵中,求最大的
子矩阵,使得子矩阵中没有Forbidden标记,这个可以用上面的方法来求,但是本题有特殊
的O (NM)的方法,理论上比上面的方法要快,因为没有递归,但实际效果......