1. 程式人生 > >5033(詳解+維護斜率的單調棧+角度計算)

5033(詳解+維護斜率的單調棧+角度計算)

題目大意:T個測試用例,N個建築物,Q條查詢,輸出查詢的每個位置 能看到天空左右視角之和是多少,每條詢問的位置左右必定是有建築物的。

大致思路:維護一個斜率和高度(也就是斜率)的單調棧,將所有的點一起計算,將查詢點高度賦值為0即可。通過高度差與點差計算出斜率,從單邊進行計算,計算出左側的角度,將陣列反轉或者從右側出發,計算右側的角度,注意角度是與垂線之間的夾角,不是水平的。每加入一個點,維護棧內的高度和斜率,也就是這個人能看到棧內所有的建築物,如果看不到 ,被擋道就刪掉,刪除凹點即可。維護一個斜率絕對值遞增的單調棧

這種情況所有點都被 擋住了

這裡寫圖片描述

這種情況都沒有被擋住所以直接算棧頂元素即可

這裡寫圖片描述

所有的詳解以及細節都在程式碼的註釋裡,與君共勉。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100100;
int n;
#define PI acos(-1)
 //圓周率,以前並沒用過
struct node{
  int id;
  double h;
  double xi;
};
//id表示查詢的下標,其hi=0,xi=qi;
node a[2*maxn];
node s[2*maxn];
double ans[2*maxn];
int cmp(node a,node b)
{
    return a.xi<b.xi;
}
double judge(node a,node b,node c)
{
    double an1,an2;
    an1=((a.h-c.h)/(a.xi-c.xi));//(top-1,top,i);順序不要錯
    an2=((b.h-c.h)/(b.xi-c.xi));
    return an1<an2;//因為從分析單側所以所有的ans都為負。
    //返回1則表示有凹點
}
double digistangle(node a,node b)
{
    double ans;
    ans=abs((a.xi-b.xi)/(a.h-b.h));
    double angle=atan(ans);
    return angle*180/PI;//atan得出的弧度,這是轉化為角度的公式
}

//把所有的查詢點作為一個獨立的點放進去,用hi來作為判斷;
void solve(int cnt)
{  int top=0;//棧頂標記,始終指向棧頂的下一個下標。
   for(int i=0;i<cnt;i++)
   {
       if(a[i].h==0)//判斷是否為查詢點
       { // printf("top %d\n",judge(s[top],s[top-1],a[i]));
          while(top>=2&&judge(s[top-1],s[top],a[i]))
          {
            top--;
            }//同樣去除凹點;
          ans[a[i].id]+=digistangle(a[i],s[top]);
       }
       else
       {
 //  1 2
   //2 1
  // 5 1
  // 1
   //4//測試了很多資料
           while(top&&a[i].h>=s[top].h)//如果棧非空的話
            top--;//維護一個斜率以及高度的單調棧,將沒有用的凹點去掉即可
           while(top>=2&&judge(s[top-1],s[top],a[i]))
            top--;//去除凹點;
           s[++top]=a[i];
       }
   }
}
void revers(int n)
{   int mid;
    if(n%2==0)
    mid=n/2-1;
    else
        mid=n/2;
    for(int i=0;i<=mid;i++)
    {
        node p=a[i];
        a[i]=a[n-i-1];
        a[n-i-1]=p;
    }
}//手動實現的反轉
//
int main()
{

    int T;
    scanf("%d",&T);
    int flag=0;
    while(T--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
            {scanf("%lf %lf",&a[i].xi,&a[i].h);
             }
        int cas;
        int cnt=n;
        scanf("%d",&cas);
        double q;

        for(int i=0;i<cas;i++)
        {
            scanf("%lf",&q);
            a[cnt].xi=q;

            a[cnt].h=0;
            a[cnt].id=i;
            cnt++;

                    }


        sort(a,a+cnt,cmp);
        //將下標從小到大來進行排序。
          memset(ans,0,sizeof(ans));
        solve(cnt);
        reverse(a,a+cnt);
        //revers(cnt);
         for(int i=0;i<cnt;i++) a[i].xi=10000000-a[i].xi ;
        //將原本的雙邊問題轉到單邊問題,簡單的轉化,棧就是單邊的
        //並且因為judge函式判斷的是單側負數,用一個大數用來維持正負
        solve(cnt);
        printf("Case #%d:\n",++flag);
        for(int i=0;i<cas;i++)
        {
            printf("%.10lf\n",ans[i]);

        }
    }
    return 0;

}
//題目雖然不難,只是維護一個斜率的單調棧,但是裡面需要注意的點卻很多

參考部落格: