//
// acm-icpc-mono-stack.cpp
#include <iostream>
#include <cassert>
#include <vector>
struct Box { //Or name to Rect;
int width;
int height;
};
struct RangeItem {
int start_index=-1;
int end_index=-1;
int width=-1;//处理高度递降的情况是必需的
int height=-1;
long area=-1;
RangeItem(){}
RangeItem(int s, int e, int w, int h, long a):start_index(s),end_index(e),width(w), height(h),area(a){}
friend std::ostream& operator<<(std::ostream& out, const RangeItem& ri){
out<<"RangeItem ["<<ri.start_index<<", "<<ri.end_index<<"] width="<<ri.width<<" height="<<ri.height<<" area="<<ri.area;
return out;
}
};
/**
平面上N个矩形盒子底部水平对齐紧靠在一起放在地上,求其最大内切矩形的面积
提示:利用单调栈,对于盒子宽度相同(不妨假设=1)的情况,是一道ACM/ICPC题目,也是一道Google面试题
这里处理的是每个矩形的宽度高度任意的情况(面积累加使用long类型,没有考虑溢出的情况)
*/
long solve(Box boxes[], int N) {
assert(N>=0);
if (N==1)
return boxes[0].width * (long)boxes[0].height;
RangeItem max_ri_l2r(-1,-1,-1,-1,-1); //invalid
std::vector<RangeItem> L;
//stack不支持for-range语法,只好改用vector了
//L中存储的项的height是按push_back顺序递增的
for(int i=0; i<N; ++i) {
const Box& box = boxes[i];
int width = box.width;
int height = box.height;
long area = box.width * (long)height;
std::cout<<"Box["<<i<<"] width="<<width<<" height="<<height<<" area="<<area<<std::endl;
//反向扫描栈;
RangeItem max_popped_ri;
//注意:我们可以把水平线一排box视作一系列高度单调上升的range项,每次遇到一个高度下降的则产生一个max_popped_ri
long max_popped_area=-1;
if (L.size()>0) {
RangeItem& ri = L.back();
while (ri.height>height) {
//注意!这里对简单的输入4,3,2,1(width=1)的情况会产生遗漏,解决方法就是从右往左再来一遍!
if (ri.area>max_popped_area) {
max_popped_area = ri.area;
max_popped_ri = ri;
}
L.pop_back();//discard;
if(L.size()==0)
break;
}
}
if (max_popped_area>0) {
if (max_popped_ri.area > max_ri_l2r.area) {
max_ri_l2r = max_popped_ri;
std::cout<<"[L2R]Set current max_max_popped_ri="<<max_ri_l2r<<std::endl;
}
}
//现在当前box的height>=单调栈中所有的RangeItem的height,扩展栈中所有ri的end_index和area
for(RangeItem& ri2 : L) {
ri2.end_index = i;
ri2.width += width;
ri2.area += ri2.height * (long)width;
}
//如果当前box的height大于单调栈S中的top项,则它自己也扩展为一个新的RangeItem/start_index;
if (L.empty() || box.height > L.back().height) {
L.push_back(RangeItem(i, i, width, height, area));
}
}
//OK, 现在再次遍历一遍单调S(注意,S中元素最多,当原始输入的boxes的height正好是全局递增的情况)
//上面的代码跟下面的代码差别只在于一个是从左往右遍历,处理原始输入的递增子序列;一个是从右往左遍历,处理原始输入的递降子序列;
{
RangeItem& max_ri = max_ri_l2r; //rename
for(RangeItem& ri : L) {
if (ri.area > max_ri.area)
max_ri = ri;
}
max_ri_l2r = max_ri;
}
//对于递减的情况,上面的代码会导致遗漏,这时候需要从右往左再算一遍,
RangeItem max_ri_r2l(-1,-1,-1,-1,-1); //invalid
std::vector<RangeItem> R;
//stack不支持for-range语法,只好改用vector了
//R中存储的项的height是按push_back顺序递增的
for(int i=N-1; i>=0; --i) {
const Box& box = boxes[i];
int width = box.width;
int height = box.height;
long area = box.width * (long)height;
std::cout<<"Box["<<i<<"] width="<<width<<" height="<<height<<" area="<<area<<std::endl;
//反向扫描栈;
RangeItem max_popped_ri;
//注意:我们可以把水平线一排box视作一系列高度单调上升的range项,每次遇到一个高度下降的则产生一个max_popped_ri
long max_popped_area=-1;
if (R.size()>0) {
RangeItem& ri = R.back();
while (ri.height>height) {
if (ri.area>max_popped_area) {
max_popped_area = ri.area;
max_popped_ri = ri;
}
R.pop_back();//discard;
if(R.size()==0)
break;
}
}
if (max_popped_area>0) {
if (max_popped_ri.area > max_ri_r2l.area) {
max_ri_r2l = max_popped_ri;
std::cout<<"[R2L]Set current max_max_popped_ri="<<max_ri_r2l<<std::endl;
}
}
//现在当前box的height>=单调栈中所有的RangeItem的height,扩展栈中所有ri的end_index和area
for(RangeItem& ri2 : R) {
ri2.start_index = i;
ri2.width += width;
ri2.area += ri2.height * (long)width;
}
//如果当前box的height大于单调栈S中的top项,则它自己也扩展为一个新的RangeItem/start_index;
if (R.empty() || box.height > R.back().height) {
R.push_back(RangeItem(i, i, width, height, area));
}
}
//OK, 现在再次遍历一遍单调S(注意,S中元素最多,当原始输入的boxes的height正好是全局递增的情况)
{
RangeItem& max_ri = max_ri_r2l; //rename
for(RangeItem& ri : R) {
if (ri.area > max_ri.area)
max_ri = ri;
}
max_ri_r2l = max_ri;
}
RangeItem max_ri = max_ri_l2r;
if (max_ri.area < max_ri_r2l.area)
max_ri = max_ri_r2l;
std::cout<<"Max Range: ["<<max_ri.start_index<<", "<<max_ri.end_index<<"]"
<<" width="<<max_ri.width
<<" height="<<max_ri.height
<<" area="<<max_ri.area
<<"\n"<<std::endl;
return max_ri.area;
}
int main(int argc, char** argv){
//test:
static Box boxes1[] = {{1,1}, {1,2}, {1,3}, {1,4}};
solve(boxes1, 4);//6
static Box boxes2[] = {{1,1}, {10,2}, {5,3}, {1,4}};
solve(boxes2, 4);//32
static Box boxes3[] = {{1,4}, {1,3}, {1,2}, {1,1}};
solve(boxes3, 4);//6
//TODO: 需要更多的测试数据
return 0;
}
TODO: FIXME, 上面的代码有2段模式类似的部分,不同点在于循环的方向不一样,可以提取出一个单独的函数来吗?