hdu 1542 Atlantis


線段樹求矩形面積並

經典題目,poj 1151 是相同的題目。終於學了求矩形面積並,詳細說一下。

首先是看小hh的線段樹專題,因為找不到什么論文來看所以只好啃他的代碼,啃了一個晚上,有感覺,但是不確定,只能輕輕體會到掃描線的意義。后來啃不下去了,就自己想,給想了出來,但是想出來居然是跟原始的方法不同的。所以下面說的是原始的方法(或者說是小hh代碼中的方法),以及我自己想出來的一種方法,兩種雖然不同,但是個人感覺本質還是差不多的,不過從效率上看,小hh的那種代碼應該效率更高。另外下面給出的代碼都是用線段樹來模擬掃描法,其實還有更好的方法就是用DP的思想去優化,據說效率提高不是一點兩點而是很多,但是還沒學,學完會繼續更新

 

分析:

1.矩形比較多,坐標也很大,所以橫坐標需要離散化(縱坐標不需要),熟悉離散化后這個步驟不難,所以這里不詳細講解了,不明白的還請百度

2.重點:掃描線法:假想有一條掃描線,從左往右(從右往左),或者從下往上(從上往下)掃描過整個多邊形(或者說畸形。。多個矩形疊加后的那個圖形)。如果是豎直方向上掃描,則是離散化橫坐標,如果是水平方向上掃描,則是離散化縱坐標。下面的分析都是離散化橫坐標的,並且從下往上掃描的

   掃描之前還需要做一個工作,就是保存好所有矩形的上下邊,並且按照它們所處的高度進行排序,另外如果是上邊我們給他一個值-1,下邊給他一個值1,我們用一個結構體來保存所有的上下邊 

struct segment
{
double l,r,h;   //l,r表示這條上下邊的左右坐標,h是這條邊所處的高度
int f;   //所賦的值,1或-1
}

接着掃描線從下往上掃描,每遇到一條上下邊就停下來,將這條線段投影到總區間上(總區間就是整個多邊形橫跨的長度),這個投影對應的其實是個插入和刪除線段操作。還記得給他們賦的值1或-1嗎,下邊是1,掃描到下邊的話相當於往總區間插入一條線段,上邊-1,掃描到上邊相當於在總區間刪除一條線段(如果說插入刪除比較抽象,那么就直白說,掃描到下邊,投影到總區間,對應的那一段的值都要增1,掃描到上邊對應的那一段的值都要減1,如果總區間某一段的值為0,說明其實沒有線段覆蓋到它,為正數則有,那會不會為負數呢?是不可能的,可以自己思考一下)。

每掃描到一條上下邊后並投影到總區間后,就判斷總區間現在被覆蓋的總長度,然后用下一條邊的高度減去當前這條邊的高度,乘上總區間被覆蓋的長度,就能得到一塊面積,並依此做下去,就能得到最后的面積

(這個過程其實一點都不難,只是看文字較難體會,建議紙上畫圖,一畫即可明白,下面獻上一圖希望有幫組)

從這個圖,也可以感受到,就好比一個畸形的容器,往里面倒水,從最下面往上面漲,被水淹過的部分其實就是我們要求的面積

 

下面給出代碼

/*
1.保存矩形的上下邊界,並且重要的,記錄他們是屬於上還是下,然后按高度升序排序
2.保存豎線坐標,並且去重,是為了離散化
3.以保存的上下邊界數組去更新
*/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3
#define MAX 110
#define LCH(i) ((i)<<1)
#define RCH(i) ((i)<<1 | 1)

struct segment //保存矩形上下邊界
{
  double l,r,h; //左右橫坐標,縱坐標
  int f; //-1為下邊界,1為上邊界
}ss[2*MAX];
struct node //線段樹節點
{
  int l,r;
  int cnt; //該節點被覆蓋的情況
  double len; //該區間被覆蓋的總長度
  int mid()
  { return (l+r)>>1; }
}tt[2*MAX*4];
double pos[2*MAX];
int nums;

int cmp(struct segment a ,struct segment b)
{
  return a.h<b.h;
}

void build(int a, int b ,int rt)
{
 tt[rt].l=a; tt[rt].r=b; tt[rt].cnt=0; tt[rt].len=0;
 if(a==b) return ;
 int mid=tt[rt].mid();
 build(a,mid,LCH(rt));
 build(mid+1,b,RCH(rt));
}

int binary(double key ,int low, int high)
{
   while(low<=high)
   {
      int mid=(low+high)>>1;
      if(pos[mid] == key) return mid;
      else if(key < pos[mid]) high=mid-1;
      else                    low=mid+1;
   }
   return -1;
}

void get_len(int rt)
{
   if(tt[rt].cnt) //非0,已經被整段覆蓋
      tt[rt].len = pos[tt[rt].r+1] - pos[tt[rt].l];
   else if(tt[rt].l == tt[rt].r) //已經不是一條線段
      tt[rt].len = 0;
   else //是一條線段但是又沒有整段覆蓋,那么只能從左右孩子的信息中獲取
      tt[rt].len = tt[LCH(rt)].len + tt[RCH(rt)].len ;
}

void updata(int a, int b ,int val ,int rt)
{
   if(tt[rt].l==a && tt[rt].r==b) //目標區間
   {
      tt[rt].cnt += val; //更新這個區間被覆蓋的情況
      get_len(rt);  //更新這個區間被覆蓋的總長度
      return ;
   }
   int mid=tt[rt].mid();
   if(b<=mid) //只訪問左孩子
      updata(a,b,val,LCH(rt));
   else if(a>mid) //只訪問有孩子
      updata(a,b,val,RCH(rt));
   else //左右都要訪問
   {
      updata(a,mid,val,LCH(rt));
      updata(mid+1,b,val,RCH(rt));
   }
   get_len(rt); //計算該區間被覆蓋的總長度
}

int main()
{
  int Case=0;
  int n;
  while(scanf("%d",&n)!=EOF && n)
  {
    nums=0;
    for(int i=0; i<n; i++)
    {
      double x1,y1,x2,y2;
      scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
      ss[nums].l=x1;  ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=1;
      //記錄上邊界的信息
      ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=-1;
      //記錄下邊界的信息
      pos[nums]=x1; pos[nums+1]=x2;
      //記錄橫坐標
      nums += 2;

    }

    sort(ss,ss+nums,cmp); //橫線按縱坐標升序排序
    sort(pos,pos+nums); //橫坐標升序排序
    //for(int i=0; i<nums; i++) printf("%.2lf %.2lf  %.2lf\n",ss[i].l,ss[i].r,ss[i].h);
    int m=1;
    for(int i=1; i<nums; i++)
      if(pos[i]!=pos[i-1]) //去重
        pos[m++]=pos[i];

    build(0,m-1,1);  //離散化后的區間就是[0,m-1],以此建樹
    double ans=0;
    for(int i=0; i<nums; i++) //拿出每條橫線並且更新
    {
       int l=binary(ss[i].l,0,m-1);
       int r=binary(ss[i].r,0,m-1)-1;
       updata(l,r,ss[i].f,1); //用這條線段去更新
       ans += (ss[i+1].h-ss[i].h)*tt[1].len;
       //printf("%.2lf\n",ans);
    }
    printf("Test case #%d\n",++Case);
    printf("Total explored area: %.2f\n\n",ans);
  }
  return 0;
}

 

————————————————————————————————————————————————————————————————————————————

下面說一下我自己理解出來的一個方法,當時是還沒有明白上面的代碼及其思想的時候想出來的

1.離散化橫坐標,從下往上掃描上下邊,一樣要排序,一樣給下邊賦值1,上邊賦值-1

2.沒掃描到一條上下邊,把它投影到總區間,但不是算總區間被覆蓋的總長度。而是這條邊界投影后,看這條邊界對應的區間內,哪些部分對應的值變為了0(那個1和-1疊加后會變回0),變為0的部分就可以乘上高度差得到一小塊的面積

這種方法還要記錄一個值,就是總區間上每一段對應的最低高度,當某一段沒有被線段覆蓋時,它的最低高度是0,如果一旦被一個邊界覆蓋了,它的最低高度就是這條邊界的高度(而且可以知道這個邊界一定是下邊界,不會是上邊界首先覆蓋的,這個道理和上面的一樣),而已經被覆蓋的線段,如果再給其他邊界覆蓋,無論是增加還是消除,其最低高度都不變。除非是完全消掉,那么它的最低高度又變回0

 

這個過程其實也不難理解的,但是文字真心難理解,建議自己畫圖,很容易明白,下面再獻上一圖,希望有幫助

 

下面給出兩個代碼,都是實現上面的思想的,第一種用了LAZY思想,效率比第二個高,第二個是不加思索地深入到每一片葉子再求面積。但是在oj都跑出了0ms,只能說數據水了。。。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define MAX 110
#define LCH(i) ((i)<<1)
#define RCH(i) ((i)<<1|1)

struct segment
{
   double l,r,h;
   int f;
}ss[2*MAX];
struct node
{
   int l,r;
   double h;
   int cnt;
   int mid()
   { return (l+r)>>1; }
}tt[2*MAX*4];
double pos[2*MAX];
double ans;
int nums;

int cmp(struct segment a ,struct segment b)
{
   return a.h<b.h;
}

void build(int a ,int b ,int rt)
{
   tt[rt].l=a; tt[rt].r=b; tt[rt].h=0; tt[rt].cnt=INF;
   if(a==b) return ;
   int mid=tt[rt].mid();
   build(a,mid,LCH(rt));
   build(mid+1,b,RCH(rt));
}

int binary(double key ,int low, int high)
{
   while(low<=high)
   {
      int mid=(low+high)>>1;
      if(pos[mid] == key) return mid;
      else if(key < pos[mid]) high=mid-1;
      else                    low=mid+1;
   }
   return -1;
}

/*
一個線段進來,是將該線段對應的黨員的值都增加val,而不是變為val
所以並不是找到目標區間就停止了,而在找到了目標區間的基礎上,還要保證該區間各單元的值都相等
那么才可以成段更新,因此找到了目標區間還要繼續深入(而且可知深入進去都必定是目標區間的子區間)
在最后找到了可改變值的區間時,就進去求面積函數
*/

void cal(int val ,int rt ,int n)
{
   if(tt[rt].cnt + val == 0) //可以計算面積
   {
      ans += (pos[tt[rt].r+1]-pos[tt[rt].l])*(ss[n].h-tt[rt].h);
      tt[rt].cnt=0;
   }
   else if(tt[rt].cnt == 0 && val==-1) //加入底線
   {
      tt[rt].h=ss[n].h;
      tt[rt].cnt=-1;
   }
   else if(tt[rt].cnt==INF)
   {
      tt[rt].cnt = val;
      tt[rt].h=ss[n].h;
   }
   else
      tt[rt].cnt+=val;
   return ;
}

void updata(int a ,int b ,int val ,int rt , int n)
{
   int mid;
   if(tt[rt].l==a && tt[rt].r==b) //找到了目標區間但是還不能改變區間值
   {
      if(tt[rt].cnt!=INF || tt[rt].l==tt[rt].r) //整段的值都是一樣的,可以更新了
      {
         cal(val,rt,n); //先進入求面積函數
      }
      else //整段的值不同,那么還要繼續深入
      {
         mid=tt[rt].mid();
         updata(a,mid,val,LCH(rt),n);
         updata(mid+1,b,val,RCH(rt),n);
      }
      return ;
   }
   mid=tt[rt].mid();
   if(tt[rt].cnt!=INF) //當前區間的數值是統一的,要傳遞給左右孩子
   {
      tt[LCH(rt)].cnt=tt[RCH(rt)].cnt=tt[rt].cnt;
      tt[rt].cnt=INF;
   }
   if(b<=mid) //左孩子
      updata(a,b,val,LCH(rt),n);
   else if(a>mid)
      updata(a,b,val,RCH(rt),n);
   else
   {
      updata(a,mid,val,LCH(rt),n);
      updata(mid+1,b,val,RCH(rt),n);
   }
}

int main()
{
   int Case=0;
   int n;
   while(scanf("%d",&n)!=EOF && n)
   {
      double x1,x2,y1,y2;
      nums=0;
      for(int i=0; i<n; i++)
      {
         scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
         ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=-1;
         ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=1;
         pos[nums]=x1; pos[nums+1]=x2;
         nums += 2;
      }
      sort(ss,ss+nums,cmp);
      sort(pos,pos+nums);
      int m=1;
      for(int i=1; i<nums; i++)
         if(pos[i]!=pos[i-1])
            pos[m++]=pos[i];
      build(0,m-1,1);
      ans=0;
      for(int i=0; i<nums; i++) //拿出每條橫線並且更新
      {
       int l=binary(ss[i].l,0,m-1);
       int r=binary(ss[i].r,0,m-1)-1;
       updata(l,r,ss[i].f,1,i); //用這條線段去更新
       //printf("%.2lf\n",ans);
      }
      printf("Test case #%d\n",++Case);
      printf("Total explored area: %.2lf\n\n",ans);
   }
   return 0;
}

 

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define MAX 110

int LCH(int n)
{ return n<<1; }
int RCH(int n)
{ return n<<1|1; }

struct segment
{
   double l,r,h;
   int f;
}ss[2*MAX];
struct node
{
   int l,r;
   double h;
   int cnt;
   int mid()
   { return (l+r)>>1; }
}tt[2*MAX*4];
double pos[2*MAX];
double ans;
int nums;

int cmp(struct segment a ,struct segment b)
{
   return a.h<b.h;
}

void build(int a ,int b ,int rt)
{
   tt[rt].l=a; tt[rt].r=b; tt[rt].h=0; tt[rt].cnt=0;
   if(a==b) return ;
   int mid=tt[rt].mid();
   build(a,mid,LCH(rt));
   build(mid+1,b,RCH(rt));
}

int binary(double key ,int low, int high)
{
   while(low<=high)
   {
      int mid=(low+high)>>1;
      if(pos[mid] == key) return mid;
      else if(key < pos[mid]) high=mid-1;
      else                    low=mid+1;
   }
   return -1;
}

void cal(int val ,int rt ,int n)
{
   if(tt[rt].cnt==0 && val==-1)
   {
      tt[rt].cnt = val;
      tt[rt].h=ss[n].h;
      return ;
   }
   if(tt[rt].cnt + val == 0)
   {
      ans += (pos[tt[rt].r+1]-pos[tt[rt].l])*(ss[n].h-tt[rt].h);
      tt[rt].cnt=0;
      tt[rt].h=0;
      return ;
   }
   tt[rt].cnt += val;
   return ;
}

void updata(int a ,int b ,int val ,int rt ,int n) //一直更新到葉子
{
   if(tt[rt].l == tt[rt].r) //到達葉子
   {
      cal(val,rt,n);
      return ;
   }
   int mid=tt[rt].mid();
   if(b<=mid) //左孩子
      updata(a,b,val,LCH(rt),n);
   else if(a>mid) //右孩子
      updata(a,b,val,RCH(rt),n);
   else
   {
      updata(a,mid,val,LCH(rt),n);
      updata(mid+1,b,val,RCH(rt),n);
   }
}

int main()
{
   int Case=0;
   int n;
   while(scanf("%d",&n)!=EOF && n)
   {
      double x1,x2,y1,y2;
      nums=0;
      for(int i=0; i<n; i++)
      {
         scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
         ss[nums].l=x1; ss[nums].r=x2; ss[nums].h=y1; ss[nums].f=-1;
         ss[nums+1].l=x1; ss[nums+1].r=x2; ss[nums+1].h=y2; ss[nums+1].f=1;
         pos[nums]=x1; pos[nums+1]=x2;
         nums += 2;
      }
      sort(ss,ss+nums,cmp);
      sort(pos,pos+nums);
      int m=1;
      for(int i=1; i<nums; i++)
         if(pos[i]!=pos[i-1])
            pos[m++]=pos[i];
      build(0,m-1,1);
      ans=0;
      for(int i=0; i<nums; i++) //拿出每條橫線並且更新
      {
       int l=binary(ss[i].l,0,m-1);
       int r=binary(ss[i].r,0,m-1)-1;
       updata(l,r,ss[i].f,1,i); //用這條線段去更新
       //printf("%.2lf\n",ans);
      }
      printf("Test case #%d\n",++Case);
      printf("Total explored area: %.2f\n\n",ans);
   }
   return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM