1. 程式人生 > >hdu 5033 buiding(單調棧)

hdu 5033 buiding(單調棧)

com atan namespace 頂上 嚴格 include query 關系 位置

hdu 5033 buiding(單調棧)

某年某月某天,馬特去了一個小鎮。這個小鎮如此狹窄,以至於他可以把小鎮當作一個樞紐。在鎮上有一些摩天大樓,其中一棟位於xi,高度為hi。所有的摩天大樓位於不同的地方。為了簡化題目,假設摩天大樓沒有寬度。由於摩天大樓如此之高,馬特幾乎看不到天空。對於馬特所在的位置,他想知道他能看到天空的角度範圍有多大。假設馬特的身高是0。可以保證,對於每個查詢,馬特的左右兩邊至少有一座建築物,而且他的位置上沒有建築物。建築物的數量n<1e5,查詢次數Q<1e5。

我用常規思路,想不出什麽好方法。既然這道題的普遍解法是單調棧,那麽我們就來看一下單調棧怎麽實現。首先,單調隊列和單調棧的前提是,加入的元素滿足一種有序的關系。所以我們自然而然地就想到,將所有查詢排序,從左向右和從右向左分別進行查詢的計算。以從左向右為例,如何做到在(均攤)O1的時間內找到一個查詢的答案呢?我們先來找些性質。

性質1:如果一個建築物i比它左側的j高,那麽當i和j都在馬特左側時,馬特一定看不到j。也就是說,如果維護一個關於建築物下標的單調棧,新加進來一個建築時,可以把它左側所有更矮的建築刪掉。那麽,現在的單調棧就是單調遞減的。這就去除了第一種冗余狀態。

性質2:從左向右而言,如果有這樣的情況:

技術分享圖片

,也就是單調棧中的頂上兩個建築和新加進來的建築組成一個凹包(不好意思,下凸包),那麽可以直接把單調棧頂的建築刪了,一直循環刪下去。因為無論馬特怎麽站,都不會被它擋住。第二種冗余狀態也被我們去除了。那麽現在,單調棧存儲的建築就組成了一個上凸包。

性質3:然而,優化力度還不夠大。這樣子算法依然不是O(n)的。那怎麽辦呢?

技術分享圖片

由於馬特一直往右跑,那麽他與凸包相切的點只會往左移動:

技術分享圖片

所以,在加入新的建築之後,判斷一下馬特與當前凸包的交點,然後將交點右側的建築全刪了(彈出)即可。棧頂的建築就是最優建築(切線嘛)。這就去除了第三種冗余狀態。然而這樣的時間復雜度是均攤O(1)的嗎?額,顯然的。

這可能是目前為止我寫的最詳細的一篇博文。。

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=1e5+5;
const double pi=3.1415926535898
; struct stack{ int t, a[maxn]; void push(int x){ a[++t]=x; } void pop(){ if (--t==-1) ++t; } void reset(){ t=0; } inline int top(){ return a[t]; } inline int top2(){ return a[t-1]; } }s; struct query{ int id, ans1, ans2; double x; }q[maxn], q2[maxn]; struct building{ double x, h; }b[maxn]; int n, T, Q; bool cmp1(query &a, query &b){ return a.x<b.x; } bool cmp2(query &a, query &b){ return a.id<b.id; } bool cmpb(building &a, building &b){ return a.x<b.x; } double angle(double x, double y){ return atan(x/y)*180/pi; } int main(){ scanf("%d", &T); int tmp=0; while (T--){ ++tmp; s.reset(); scanf("%d", &n); for (int i=1; i<=n; ++i) scanf("%lf%lf", &b[i].x, &b[i].h); scanf("%d", &Q); for (int i=1; i<=Q; ++i){ scanf("%lf", &q[i].x); q[i].id=i; } sort(b+1, b+n+1, cmpb); sort(q+1, q+Q+1, cmp1); int nowbui=1; s.push(1); for (int i=1; i<=Q; ++i){ while (b[nowbui+1].x<q[i].x){ //從左到右加入建築物 ++nowbui; //第一種冗余狀態:左邊的樓比右邊的矮 while (s.t>0&&b[s.top()].h<=b[nowbui].h) s.pop(); //所以現在這個建築物序列嚴格下降 //第二種冗余狀態:是個下凸函數 while (s.t>1&&(b[nowbui].h-b[s.top()].h)/ (b[nowbui].x-b[s.top()].x)>= (b[s.top()].h-b[s.top2()].h)/ (b[s.top()].x-b[s.top2()].x)) s.pop(); //所以現在這個建築物序列是凸包了 s.push(nowbui); } //第三種冗余狀態:若一棟樓在視線所切的那棟樓的右邊,則可以刪去 //可以用均攤,證明這裏的復雜度是常數 while (s.t>1&&(b[s.top2()].h)/(q[i].x-b[s.top2()].x)> (b[s.top()].h)/(q[i].x-b[s.top()].x)) s.pop(); //現在終於不僅排除了冗余狀態,並且找到了最優解。 q[i].ans1=s.top(); } //從右往左也一樣 nowbui=n; s.reset(); s.push(n); for (int i=Q; i>=1; --i){ while (b[nowbui-1].x>q[i].x){ --nowbui; //右邊的樓比左邊的矮 while (s.t>0&&b[s.top()].h<=b[nowbui].h) s.pop(); //下面有圖 (其實這裏封裝一個關於斜率的函數會更好) while (s.t>1&&(b[nowbui].h-b[s.top()].h)/ (b[nowbui].x-b[s.top()].x)<= (b[s.top()].h-b[s.top2()].h)/ (b[s.top()].x-b[s.top2()].x)) s.pop(); s.push(nowbui); } //下面有圖 while (s.t>1&&b[s.top2()].h/(b[s.top2()].x-q[i].x)> b[s.top()].h/(b[s.top()].x-q[i].x)) s.pop(); q[i].ans2=s.top(); } sort(q+1, q+Q+1, cmp2); printf("Case #%d:\n", tmp); for (int i=1; i<=Q; ++i){ printf("%.5lf\n", 180-angle(b[q[i].ans1].h, q[i].x-b[q[i].ans1].x)-angle(b[q[i].ans2].h, b[q[i].ans2].x-q[i].x)); } } // printf("%.6lf", angle(1, 1)); return 0; }

hdu 5033 buiding(單調棧)