线段树扫描线总结:矩形面积并&面积交&周长交

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦

目录

面积并

  • 把每个矩形分成上下两条边,记录左右端点和高度,从下向上扫描,线段树维护x轴上的有效长度。
  • 每次累加部分面积:
    Δ h × l e n
    Δ h = h i g h h i g h , 线 l e n
  • 矩形下边标记为1,下边标记为-1.
  • 线段树每个叶子节点表示的是区间 [ r t , r t + 1 ) 的线段长度。( r t 表示的是离散化后的标号)
  • 这样每次 u p d a t e ( l , r 1 ) p u s h _ u p 的时候, l e n = a l l [ r + 1 ] a l l [ l ] ,因为你更新的时候右下标减了1,所以计算的时候要把1补回来。
  • 为什么每个节点表示的是 [ r t , r t + 1 ) 这段长度呢?这样 u p d a t e ( l , r 1 ) 呢?
  • 这样做是为了避免漏掉一段线段的情况,类似于POJ 2528这题的离散化要加一的操作。类似但不完全一样。


AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<assert.h>
#include<bitset>
#define lson rt<<1
#define rson rt<<1|1
#define lsonl l,mid,rt<<1
#define rsonr mid+1,r,rt<<1|1
#define lowbit(x) (x)&(-(x))
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int N = (int)1e5 +107;
struct lp{
  double l,r,h;
  int id;
}seg[N];
bool cmp(const lp &a, const lp &b){
  return a.h<b.h;
}
int n, m, tot;
int cnt[N<<2];
double sum[N<<2], all[N];
void push_up(int l,int r,int rt){
  if(cnt[rt]) sum[rt] = all[r+1]-all[l];
  else if(l==r) sum[rt] = 0;
  else sum[rt]=sum[lson]+sum[rson];
}
void update(int L,int R,int c,int l,int r,int rt){
  if(L<=l&&r<=R){
    cnt[rt] += c;
    push_up(l,r,rt);
    return;
  }
  int mid = (l+r)>>1;
  if(L>mid) update(L,R,c,rsonr);
  else if(R<=mid) update(L,R,c,lsonl);
  else {
    update(L,R,c,lsonl);update(L,R,c,rsonr);
  }
  push_up(l,r,rt);
}
int main(){
  int tc=0;
  while(~scanf("%d",&n)&&n){
    for(int i=0;i<n;++i){
      double x1,x2,y1,y2;
      scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
      seg[i].l=x1;seg[i].r=x2;seg[i].h=y1;seg[i].id=1;
      seg[i+n].l=x1;seg[i+n].r=x2;seg[i+n].h=y2;seg[i+n].id=-1;
      all[i]=x1,all[i+n]=x2;
    }
    m = 2*n;
    sort(seg,seg+m,cmp);
    sort(all,all+m);
    int k=0;
    for(int i=1;i<m;++i){
      if(all[i]!=all[i-1])all[++k]=all[i];
    }
    memset(cnt,0,sizeof(cnt));
    memset(sum,0,sizeof(sum));
    double ans = 0;
    for(int i=0;i<n*2-1;++i){
      int l = lower_bound(all,all+k+1,seg[i].l)-all;
      int r = lower_bound(all,all+k+1,seg[i].r)-all;
      if(l<r) update(l,r-1,seg[i].id,0,k,1);
      ans += sum[1]*(seg[i+1].h-seg[i].h);
    }
    printf("Test case #%d\n", ++tc);
    printf("Total explored area: %.2f\n\n", ans);
  }
  return 0;
}


面积交

  • 把每个矩形分成上下两条边,记录左右端点和高度,从下向上扫描,线段树维护x轴上的有效长度。
  • 每次累加部分面积:
    Δ h × t w o
    Δ h = h i g h h i g h , 线 t w o

  • 矩形下边标记为1,下边标记为-1.
  • 线段树每个叶子节点表示的是区间 [ r t , r t + 1 ) 的线段长度。( r t 表示的是离散化后的标号)
  • t w o 维护的是出现两次的区间的长度, o n e 维护的和上一题的 l e n 一样, c n t [ r t ] 维护改段出现次数。
  • 这里需要修改一下 p u s h _ u p 函数。细节看代码。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<assert.h>
#include<bitset>
#define lson rt<<1
#define rson rt<<1|1
#define lsonl l,mid,rt<<1
#define rsonr mid+1,r,rt<<1|1
#define lowbit(x) (x)&(-(x))
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int N = (int)1e5 +107;
struct lp{
  double l,r,h;
  int id;
}seg[N];
bool cmp(const lp &a, const lp &b){
  return a.h<b.h;
}
int n, m, tot, cnt[N<<2];
double one[N<<2], two[N<<2];
double all[N];
void push_up(int l,int r,int rt){
  if(cnt[rt]>=2){
    one[rt] = two[rt] = all[r+1]-all[l];
  }else if(cnt[rt]==1){
    one[rt] = all[r+1]-all[l];
    if(l==r)two[rt] = 0;
    else two[rt]=one[lson]+one[rson];
  }else{
    if(l==r)two[rt]=one[rt]=0;
    else{
      one[rt]=one[lson]+one[rson];
      two[rt]=two[lson]+two[rson];
    } 
  }
}
void update(int L,int R,int c,int l,int r,int rt){
  if(L<=l&&r<=R){
    cnt[rt] += c;
    push_up(l,r,rt);
    return;
  }
  int mid = (l+r)>>1;
  if(L<=mid)update(L,R,c,lsonl);
  if(R>mid)update(L,R,c,rsonr);
  push_up(l,r,rt);
}
int main(){
  int tim;
  scanf("%d",&tim);
  while(tim--){
    scanf("%d",&n);
    for(int i=0;i<n;++i){
      double x1,x2,y1,y2;
      scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
      seg[i].l=x1;seg[i].r=x2;seg[i].h=y1;seg[i].id=1;
      seg[i+n].l=x1;seg[i+n].r=x2;seg[i+n].h=y2;seg[i+n].id=-1;
      all[i]=x1,all[i+n]=x2;
    }
    m = 2*n;
    sort(seg,seg+m,cmp);
    sort(all,all+m);
    int k = unique(all, all+m)-all-1;
    memset(cnt,0,sizeof(cnt));
    memset(one,0,sizeof(one));
    memset(two,0,sizeof(two));
    double ans = 0;
    for(int i=0;i<n*2-1;++i){
      int l = lower_bound(all,all+k+1,seg[i].l)-all;
      int r = lower_bound(all,all+k+1,seg[i].r)-all;
      if(l<r) update(l,r-1,seg[i].id,0,k,1);
      ans += two[1]*(seg[i+1].h-seg[i].h);
    }
    printf("%.2f\n", ans);
  }
  return 0;
}


周长并

  • 有一种方法是维护两个线段树,每次扫描时计算本条线和上一条线的差值。
  • 当然可以只用一颗线段树就行了。
  • 横线的计算方法如上第一行所述。对于竖线:
    l e n = 2 × N u m × Δ h
  • Δ h = h i g h 线 h i g h 线 N u m = 线
  • 这里有一个特殊情况, r t 节点的左右儿子本来不相交,但是刚好重合了。这时你就要把它减掉。细节见代码。


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<assert.h>
#include<bitset>
#define lson rt<<1
#define rson rt<<1|1
#define lsonl l,mid,rt<<1
#define rsonr mid+1,r,rt<<1|1
#define lowbit(x) (x)&(-(x))
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const int N = (int)1e5 +107;
struct lp{
  double l,r,h;
  int id;
}seg[N];
struct lh{
  int l,r;
  double len;//有效长度
  int s;//被覆盖次数
  int lc,rc;//左右端点是否被覆盖
  int num;//区间有多少条线段
}cw[N<<2];
bool cmp(const lp &a, const lp &b){
  return a.h<b.h;
}
int n, m;
double all[N<<2];
void build(int l,int r,int rt){
  cw[rt].l=l;cw[rt].r=r;
  cw[rt].len=0;
  cw[rt].s=cw[rt].lc=cw[rt].rc=cw[rt].num=0;
  if(l==r)return;
  int mid = (l+r)>>1;
  build(lsonl);build(rsonr);
}
void push_up(int rt){
  if(cw[rt].s){
    cw[rt].lc=cw[rt].rc=1;
    cw[rt].len = all[cw[rt].r+1]-all[cw[rt].l];
    cw[rt].num=1;
  }else if(cw[rt].l==cw[rt].r){
    cw[rt].lc=cw[rt].rc=0;
    cw[rt].len=0;
    cw[rt].num=0;
  }else{
    cw[rt].lc=cw[lson].lc;
    cw[rt].rc=cw[rson].rc;
    cw[rt].len=cw[lson].len+cw[rson].len;
    cw[rt].num = cw[lson].num+cw[rson].num-(cw[lson].rc&cw[rson].lc);
  }
}
void update(int L,int R,int c,int rt){
  int l = cw[rt].l, r = cw[rt].r, mid=(l+r)>>1;
  if(L<=l&&r<=R){
    cw[rt].s += c;
    push_up(rt);
    return;
  }
  if(L<=mid)update(L,R,c,lson);
  if(R>mid)update(L,R,c,rson);
  push_up(rt);
}
int main(){
  while(~scanf("%d",&n)){
    for(int i=0;i<n;++i){
      double x1,x2,y1,y2;
      scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
      seg[i].l=x1;seg[i].r=x2;seg[i].h=y1;seg[i].id=1;
      seg[i+n].l=x1;seg[i+n].r=x2;seg[i+n].h=y2;seg[i+n].id=-1;
      all[i]=x1,all[i+n]=x2;
    }
    m = 2*n;
    sort(seg,seg+m,cmp);
    sort(all,all+m);
    int k = unique(all, all+m)-all-1;
    double ans = 0;
    double last=0;
    build(0,k,1);
    for(int i=0;i<n*2;++i){
      int l = lower_bound(all,all+k+1,seg[i].l)-all;
      int r = lower_bound(all,all+k+1,seg[i].r)-all;
      if(l<r) update(l,r-1,seg[i].id,1);
      ans += fabs(cw[1].len-last);
      last = cw[1].len;
      ans += cw[1].num*2*(seg[i+1].h-seg[i].h);
    }
    printf("%.0f\n", ans);
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39599067/article/details/81317336
今日推荐