hdu 5033 buiding(單調棧)
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(單調棧)