并矩形覆盖的面积

 *************************

优秀博客系列: http://blog.csdn.net/dooder_daodao/article/details/6334169


**************************

Poj1151/HDU1542(求并矩形覆盖的面积)分别用离散化处理,矩形切割,线段树三种方法实现.

题目链接:http://poj.org/problem?id=1151

http://acm.hdu.edu.cn/showproblem.php?pid=1542

题意:给出任意n多矩形,求其总覆盖的面积.

可分别用离散化处理,矩形切割,线段树三种方法实现,以下分别为三种方法的代码:

离散化坐标:

#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>
#define M 408
const double eps = 1e-8;
typedef struct{
	double x1,x2,y1,y2;
	void set(double x1,double y1,double x2,double y2)
	{this->x1=x1;this->y1=y1;this->x2=x2;this->y2=y2;}
}Rec;
Rec r[M];
bool map[M][M];
//数组开太小,RE了一次
double x[M],y[M];
int n,nx,ny;
int Cmp(const void *a,const void *b)
{
	return *(double *)a>*(double *)b?1:-1;
}
int Find(double arr[],int low,int up,double v)
{//二分查找,并且一定数组中一定存在与v相等的值
	int mid=(low+up)>>1;
	if(fabs(arr[mid]-v)<eps) return mid;
	if(arr[mid]>v) return Find(arr,low,mid-1,v);
	return Find(arr,mid+1,up,v);
}
int main()
{
	double x1,y1,x2,y2,ans;
	int t=1,i,j,i1,i2,j1,j2,k;
	while(scanf("%d",&n),n){
		for(nx=ny=0,i=0;i<n;i++){
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			r[i].set(x1,y1,x2,y2);
			x[nx++]=x1;x[nx++]=x2;
			y[ny++]=y1;y[ny++]=y2;
		}
		//排序,进而离散化点
		qsort(x,nx,sizeof(x[0]),Cmp);
		qsort(y,ny,sizeof(y[0]),Cmp);
		//消除相等的值,进行离散化
		for(i=0,j=0;i<nx;i++){
			if(fabs(x[i]-x[j])<eps) continue;
			x[++j]=x[i];
		}nx=j;
		for(i=0,j=0;i<ny;i++){
			if(fabs(y[i]-y[j])<eps) continue;
			y[++j]=y[i];
		}ny=j;
		memset(map,false,sizeof(map));
		for(i=0;i<n;i++){
			i1=Find(x,0,nx,r[i].x1);
			i2=Find(x,0,nx,r[i].x2);
			j1=Find(y,0,ny,r[i].y1);
			j2=Find(y,0,ny,r[i].y2);
			for(j=i1;j<i2;j++){//标记覆盖的区域
				for(k=j1;k<j2;k++)
					map[j][k]=true;
			}
		}
		ans=0.0;
		for(i=0;i<nx;i++){
			for(j=0;j<ny;j++){
				if(map[i][j])
					ans+=(x[i+1]-x[i])*(y[j+1]-y[j]);
			}
		}
		printf("Test case #%d/n",t++);
		printf("Total explored area: ");
		printf("%.2f/n/n",ans);
	}
	return 0;
}


矩形切割:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define M 108
typedef struct talRec{
	double x1,y1,x2,y2;
	talRec *next;
}Rec;
Rec space[M*24];//手动分配空间
Rec *list;
int n;
double min(double a,double b)
{
	return a<b?a:b;
}
double max(double a,double b)
{
	return a>b?a:b;
}
void Init()
{//重置内存空间
	int i;
	for(i=0;i<24*n;i++)
		space[i].next=&space[i+1];
	list=&space[1];list->next=NULL;
	space[0].next=&space[2];
}
void Del(Rec *cur)
{//把删掉的结点放入内存分配的链表中
	cur->next=space[0].next;
	space[0].next=cur;
}
void Add(double x1,double y1,double x2,double y2)
{//向链表中插入新矩形,头插法
	
	Rec *cur;
	//new Rec *
	cur=space[0].next;//申请新结点
	space[0].next=cur->next;
	
	cur->x1=x1;cur->y1=y1;
	cur->x2=x2;cur->y2=y2;
	cur->next=list->next;
	list->next=cur;
}
void Cut(double x1,double y1,double x2,double y2,
		 double x3,double y3,double x4,double y4)
{//其中点分别为矩形左下侧点和右上侧点
	double k1,k2,k3,k4;
	
	if(x2<=x3||y2<=y3||x4<=x1||y4<=y1){
		Add(x1,y1,x2,y2);//如果不相交
		return ;
	}
	k1=max(x1,x3);k2=min(x2,x4);
	if(x1<k1) Add(x1,y1,k1,y2);
	if(x2>k2) Add(k2,y1,x2,y2);
	k3=k1,k4=k2;
	k1=max(y1,y3);k2=min(y2,y4);
	if(y1<k1) Add(k3,y1,k4,k1);
	if(y2>k2) Add(k3,k2,k4,y2);
}
void New(double x1,double y1,double x2,double y2)
{//添加新矩形时,与之前的矩形比较,切割
	Rec *cur,*pre,*tmp;
	
	pre=list;cur=pre->next;
	while(cur){
		pre->next=cur->next;//删掉当前与之比较的链表中的矩形
		if(pre==list){
			tmp=cur->next;
			Cut(cur->x1,cur->y1,cur->x2,cur->y2,x1,y1,x2,y2);
			while(pre->next!=tmp) pre=pre->next;
		}
		else Cut(cur->x1,cur->y1,cur->x2,cur->y2,x1,y1,x2,y2);
		Del(cur);
		cur=pre->next;
	}
	Add(x1,y1,x2,y2);
}
int main()
{
	double x1,y1,x2,y2,ans,tmp;
	int t=1;
	while(scanf("%d",&n),n){
		Init();//
		while(n--){
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			if(x1>x2) tmp=x1,x1=x2,x2=tmp;
			if(y1>y2) tmp=y1,y1=y2,y2=tmp;
			New(x1,y1,x2,y2);
		}
		for(list=list->next,ans=0.0;list;list=list->next)
			ans+=(list->x2-list->x1)*(list->y2-list->y1);
		printf("Test case #%d/n",t++);
		printf("Total explored area: ");
		printf("%.2f/n/n",ans);
	}
	return 0;
}


线段树加扫描线:

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
//部分宏定义,左子树,右子树,中间值
#define L(i) (i<<1)
#define R(i) (i<<1|1)
#define Mid(x,y) ((x+y)>>1)
#define M 2010
#define eps 1e-8
typedef struct {//垂线定义,即扫描线
	double x,y1,y2;
	int flag;
}Line;
typedef struct {//线段树结点定义
	int l,r,c;
	double ln;
}SegTree;
Line line[M];
SegTree seg[M*3];
double y[M];
int ls,ys;
int cmp1(const void *a,const void *b)
{
	Line *p=(Line *)a;
	Line *q=(Line *)b;
	if(fabs(p->x-q->x)<eps)
		return p->flag-q->flag;
	return p->x>q->x?1:-1;
}
int cmp2(const void *a,const void *b)
{
	return *(double *)a>*(double *)b?1:-1;
}
void Create(int i,int l,int r)
{//构建一个空的线段树
	seg[i].l=l;seg[i].r=r;
	seg[i].ln=0.0;seg[i].c=0;
	if(l==r-1)
		return ;
	int mid=Mid(l,r);
	Create(L(i),l,mid);
	Create(R(i),mid,r);
}
void Calen(int i)
{//更新当前结点段,y轴上覆盖的长度
	if(seg[i].c>0){
		seg[i].ln=y[seg[i].r]-y[seg[i].l];
		return ;
	}
	if(seg[i].r-seg[i].l==1)
		seg[i].ln=0.0;
	else seg[i].ln=seg[L(i)].ln+seg[R(i)].ln;
}
void Updata(int i,Line l)
{//向线段树增加新的线段
	Line tmp;
	int mid;
	if(fabs(l.y1-y[seg[i].l])<eps&&fabs(l.y2-y[seg[i].r])<eps){
		seg[i].c+=l.flag;
		Calen(i);
		return ;
	}
	mid=Mid(seg[i].l,seg[i].r);
	if(fabs(l.y2-y[mid])<eps||l.y2<y[mid])
		Updata(L(i),l);
	else if(fabs(l.y1-y[mid])<eps||l.y1>y[mid])
		Updata(R(i),l);
	else {
		tmp.flag=l.flag;
		tmp.y1=l.y1;tmp.y2=y[mid];
		Updata(L(i),tmp);
		tmp.y1=y[mid];tmp.y2=l.y2;
		Updata(R(i),tmp);
	}
	Calen(i);
}
int main()
{
	int t=1,n;
	int i,j;
	double x1,x2,y1,y2;
	double ans;
	
	while(scanf("%d",&n),n){
		ls=1;
		for(i=0;i<n;i++){
			scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
			line[ls].x=x1;line[ls].y1=y1;
			line[ls].y2=y2;line[ls].flag=1;
			line[ls+1].x=x2;line[ls+1].y1=y1;
			line[ls+1].y2=y2;line[ls+1].flag=-1;
			y[ls++]=y1;y[ls++]=y2;
		}
		qsort(line+1,ls-1,sizeof(line[0]),cmp1);
		qsort(y+1,ls-1,sizeof(y[0]),cmp2);
		
		for(i=1,j=1;i<ls;i++){//y轴坐标离散化,此处消去相同的y值
			if(fabs(y[j]-y[i])<eps)	continue;
			y[++j]=y[i];
		}ys=j;
		Create(1,1,ls-1);
		ans=0;
		for(i=1;i<ls;i++){//求面积
			ans+=seg[1].ln*(line[i].x-line[i-1].x);
			Updata(1,line[i]);
		}
		printf("Test case #%d/n",t++);
		printf("Total explored area: ");
		printf("%.2f/n/n",ans);
	}
	return 0;
}


 

其实,还有好几种做法~~~什么二维线段树,什么方块树~呃,以后再研究~


HDOJ 3265 Posters (线段树+扫描线求矩形面积并)


题意:给n个矩形,每个矩形都有一个矩形的“洞”,矩形和洞的边都与坐标轴平行,求这些带“洞”的矩形覆盖的面积。

数据范围:n<=50000, 0<=x,y<=50000

分析:这题本质还是求矩形面积并,因为一个带“洞”的矩形可以看成是4个矩形。由于矩形数目n和坐标范围均比较大,所以离散化+暴力统计的方法肯定会超时。扫描线的方法我也是第一次学,我的理解是这样的,把所有矩形的2条竖直边(横边也一样)无限延伸就得到扫描线,这些线把所有矩形重新划分为许多不相交的矩形,所有矩形的面积并其实就是夹在这些线之间的矩形的面积之和,夹缝中的矩形的宽就是扫描线之间的距离,关键在于夹缝中矩形的高度是多少?这就要用到线段树了,由于坐标范围和矩形数目是一个数量级的,所以不离散化应该也没关系(我写了离散化)。线段树是根据y轴来建的,保存的关键信息就是当前夹缝中的矩形的高,其实就是区间的覆盖长度,另需保存区间被覆盖的次数以便更新。将从左到有扫描竖线,碰到矩形的左边竖线就覆盖到区间中,碰到右边竖线就将其从区间中删除,这样的话每次查询的区间被覆盖的长度就是夹缝中矩形的高。

注意:

1、建树之前要判断y方向上的坐标数目是否小于2,小于2时若建树会RE。其实小于2说明结果是0

2、最大面积为500000*50000,超了int,可以用unsigned int

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 200010
int n;
struct scan
{
    int x,yl,yu,flag;
};
scan scanline[2*N];
int xcnt;
int cnt[4*2*N],len[4*2*N];
int y[2*N],ycnt;
int cmp1(const void *a,const void *b)
{
    return *(int*)a-*(int*)b;
}
int cmp2(const void *a,const void *b)
{
    scan *c=(scan *)a;
    scan *d=(scan *)b;
    return (c->x)-(d->x);
}
void init()
{
    xcnt=ycnt=0;
}
void add(int a,int b,int c,int d)
{
    scanline[xcnt].x=a;
    scanline[xcnt].yl=b;
    scanline[xcnt].yu=d;
    scanline[xcnt++].flag=1;

    scanline[xcnt].x=c;
    scanline[xcnt].yl=b;
    scanline[xcnt].yu=d;
    scanline[xcnt++].flag=0;

    y[ycnt++]=b;    y[ycnt++]=d;
}
void read()
{
    int i,j;
    int x[8];
    for(i=0;i<n;i++)
    {
        for(j=0;j<8;j++)    scanf("%d",&x[j]);
        if(x[0]<x[4] && x[1]<x[3])    add(x[0],x[1],x[4],x[3]);
        if(x[6]<x[2] && x[1]<x[3])    add(x[6],x[1],x[2],x[3]);
        if(x[4]<x[6] && x[7]<x[3])    add(x[4],x[7],x[6],x[3]);
        if(x[4]<x[6] && x[1]<x[5])    add(x[4],x[1],x[6],x[5]);
    }
}
int bs(int k)
{
    int min=0,max=ycnt,mid;
    while(min+1!=max)
    {
        mid=min+max>>1;
        if(y[mid]>k)    max=mid;
        else    min=mid;
    }
    return min+1;
}

void update(int cur,int l,int r)
{
    int ls=cur<<1,rs=cur<<1|1;
    if(cnt[cur]>0)  len[cur]=y[r-1]-y[l-1];
    else if(l+1==r) len[cur]=0;
    else    len[cur]=len[ls]+len[rs];
}
void build(int cur,int l,int r)
{
    int mid=l+r>>1,ls=cur<<1,rs=cur<<1|1;
    cnt[cur]=0;
    len[cur]=0;
    if(l+1==r)  return;
    build(ls,l,mid);
    build(rs,mid,r);
}
void insert(int cur,int l,int r,int s,int t)
{
    int mid=l+r>>1,ls=cur<<1,rs=cur<<1|1;
    if(l>=s && r<=t)
    {
        cnt[cur]++;
        update(cur,l,r);
        return;
    }

    if(mid>s)   insert(ls,l,mid,s,t);
    if(mid<t)   insert(rs,mid,r,s,t);
    update(cur,l,r);
}
void del(int cur,int l,int r,int s,int t)
{
    int mid=l+r>>1,ls=cur<<1,rs=cur<<1|1;
    if(l>=s && r<=t)
    {
        cnt[cur]--;
        update(cur,l,r);
        return;
    }

    if(mid>s)   del(ls,l,mid,s,t);
    if(mid<t)   del(rs,mid,r,s,t);
    update(cur,l,r);
}

void solve()
{
    qsort(y,ycnt,sizeof(y[0]),cmp1);
    qsort(scanline,xcnt,sizeof(scanline[0]),cmp2);

    int i,j,k;
    k=ycnt;
    ycnt=0;
    y[ycnt++]=y[0];
    for(i=1;i<k;i++)    if(y[ycnt-1]^y[i])  y[ycnt++]=y[i];

    if(ycnt<2)  {   puts("0");  return; }

    unsigned int ans=0;
    build(1,1,ycnt);
    for(k=0;k<xcnt-1;k++)
    {
        i=scanline[k].yl;   i=bs(i);
        j=scanline[k].yu;   j=bs(j);
        if(scanline[k].flag)    insert(1,1,ycnt,i,j);
        else    del(1,1,ycnt,i,j);

        i=scanline[k].x;
        j=scanline[k+1].x;
        ans+=len[1]*(j-i);
    }
    printf("%u\n",ans);
}
int main()
{
    while(scanf("%d",&n),n)
    {
        init();
        read();
        solve();
    }
    return 0;
}

********************************************************

UVA11983——线段树求矩形覆盖K次以上面积并

 

 

给出N个矩形(1<=N<=30,000),求被这些矩形覆盖K次以上的区域面积。

跟矩形面积并一样的思路,不过记录的节点信息多一点,除了要记录区间覆盖次数,还要记录以该节点为根的子树上,覆盖次数为i的长度s[i]有多少。因为K很小只有10,而30000*4*10完全可以承受。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值