hdu1542 Atlantis

Problem Description
There are several ancient Greek texts that contain descriptions of the fabled island Atlantis. Some of these texts even include maps of parts of the island. But unfortunately, these maps describe different regions of Atlantis. Your friend Bill has to know the total area for which maps exist. You (unwisely) volunteered to write a program that calculates this quantity.
 

Input
The input file consists of several test cases. Each test case starts with a line containing a single integer n (1<=n<=100) of available maps. The n following lines describe one map each. Each of these lines contains four numbers x1;y1;x2;y2 (0<=x1<x2<=100000;0<=y1<y2<=100000), not necessarily integers. The values (x1; y1) and (x2;y2) are the coordinates of the top-left resp. bottom-right corner of the mapped area.

The input file is terminated by a line containing a single 0. Don’t process it.
 

Output
For each test case, your program should output one section. The first line of each section must be “Test case #k”, where k is the number of the test case (starting with 1). The second one must be “Total explored area: a”, where a is the total explored area (i.e. the area of the union of all rectangles in this test case), printed exact to two digits to the right of the decimal point.

Output a blank line after each test case.
 

Sample Input
   
   
   
   
2 10 10 20 20 15 15 25 25.5 0
 

Sample Output
   
   
   
   
Test case #1 Total explored area: 180.00

这道题我想了很久,终于A了。。题目是求一些矩形合起来的面积,可以用线段树做。首先用结构体s记录所有矩形的上下边,记录边的左端点x2,右端点x3,纵坐标y,还有增量f(增量的意思是扫到上边的时候总区间减去这条边,扫到下边的时候总区间加上这条边,用1,-1表示),因为线段的坐标都是浮点数,所以要离散化。然后再开一个数组pos,记录所有线段的横坐标,排序后,判断是否有重复值,如果有就跳过,这样做是为了待会建树时方便,因为相同的值建树时没有用。然后建立线段树,用l,r维护每条线段的左右端点,cnt维护这条线段被重复覆盖了几次,0代表没有线段覆盖,大于1表示多次覆盖,len 表示这段区间被覆盖区间的实际总长度,这里对应的是真实坐标的值,并不是线段树中的整数。这道题的线段树比较特别,可以用简单dp做,只有这道题适用,不适用其他题,因为这里保证了有下边就一定有相同长度的上边。

还有最重要的一点(我这里卡了半天= =):就是更新的时候r-1,算总长度的时候r+1;

解释:因为建立线段树的时候我们子区间是这样建的mid=(l+r)/2;build(l,mid),build(mid+1,r),因为对于单纯的线段来说,子区间加起来就是整条线段,但是这里不是这样的,因为我们离散化后,mid,mid+1之间也有距离的!所以如果总长度如果由子区间两端加起来其实是pos[r]-pos[mid+1]+pos[mid]-pos[l],少了一段mid~mid+1.这里有两种方法,第一个是以点建树(即叶子节点表示一个点的左边),运用一个技巧,更新的时候r-1,算总长度的时候r+1;第二种方法是以段建树(即叶子节点表示该点到右边一点的线段,左闭右开),方法和以点建树差不多。

举个例子:要更新的是[1,5],那么我们更新[1,4],然后算的时候算[1,3],[3,4],[4,5],这样就把中间的问题解决了。

动手画一下,就懂了。

代码一:以点建树,用了一个简单动规,只适合本题
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>
using namespace std;
struct node{
	double x2,x3,y;
	int f;
}s[800];
double pos[800];
int m;

struct edge{
	int l,r,cnt;
	double len;
}b[800];

bool cmp(node a,node b){
	return a.y<b.y;
}

int find(double x)
{
	int mid,l=1,r=m;
	while(l<=r)
	{
		mid=(l+r)/2;
		if(pos[mid]>x)r=mid-1;
		else if(pos[mid]<x)l=mid+1;
		else return mid;
	}
	if(pos[l]==x)return l;
	else return r;
}

void build(int l,int r,int i)
{
	int mid;
	b[i].l=l;b[i].r=r;b[i].cnt=b[i].len=0;
	if(l==r)return;
	mid=(l+r)/2;
	build(l,mid,i*2);
	build(mid+1,r,i*2+1);
}

void getline(int i)
{
	if(b[i].cnt)b[i].len=pos[b[i].r+1]-pos[b[i].l];
	else if(b[i].l==b[i].r)b[i].len=0; //这里是叶子节点,已经不是一条线段,所以是0
	else b[i].len=b[i*2].len+b[i*2+1].len;
}

void update(int l,int r,int add,int i)
{
	int mid;
	if(b[i].l==l && b[i].r==r){
		b[i].cnt+=add;
		getline(i);return;
	}
	mid=(b[i].l+b[i].r)/2;
	if(r<=mid)update(l,r,add,i*2);
	else if(l>mid)update(l,r,add,i*2+1);
	else {
		update(l,mid,add,i*2);
		update(mid+1,r,add,i*2+1);
	}
	getline(i);
}

int main()
{
	int n,i,j,num,l,r,k=0;
	double x2,y2,x3,y3,sum;
	while(scanf("%d",&n)!=EOF && n!=0)
	{
		num=0;sum=0;
		for(i=1;i<=n;i++){
			scanf("%lf%lf%lf%lf",&x2,&y2,&x3,&y3);
			num++;
			s[num].x2=x2;s[num].x3=x3;s[num].y=y2;s[num].f=1;pos[num]=x2;
			num++;
			s[num].x2=x2;s[num].x3=x3;s[num].y=y3;s[num].f=-1;pos[num]=x3;
		}
		sort(s+1,s+1+num,cmp);
		sort(pos+1,pos+1+num);
		m=1;
		for(i=2;i<=num;i++){
			if(pos[i]!=pos[i-1]){
				m++;pos[m]=pos[i];
			}
		}
		build(1,m,1);
		s[0].y=0;
		for(i=1;i<=num;i++){
			sum+=b[1].len*(s[i].y-s[i-1].y);
			l=find(s[i].x2);
			r=find(s[i].x3)-1;
			update(l,r,s[i].f,1);
		}
		k++;
		printf("Test case #%d\n",k);
		printf("Total explored area: %.2f\n\n",sum);
	}
	return 0;
}
代码二:以段建树,用了一个简单动规,只适合本题
#include<stdio.h>  
#include<string.h>  
#include<math.h>  
#include<vector>  
#include<map>  
#include<queue>  
#include<stack>  
#include<string>  
#include<algorithm>  
using namespace std;  
struct node{  
    double x2,x3,y;  
    int f;  
}s[800];  
double pos[800];  
int m;  
  
struct edge{  
    int l,r,cnt;  
    double len;  
}b[800];  
  
bool cmp(node a,node b){  
    return a.y<b.y;  
}  
  
int find(double x)  
{  
    int mid,l=1,r=m;  
    while(l<=r)  
    {  
        mid=(l+r)/2;  
        if(pos[mid]>x)r=mid-1;  
        else if(pos[mid]<x)l=mid+1;  
        else return mid;  
    }  
    if(pos[l]==x)return l;  
    else return r;  
}  
  
void build(int l,int r,int i)  
{  
    int mid;  
    b[i].l=l;b[i].r=r;b[i].cnt=b[i].len=0;  
    if(l==r)return;  
    mid=(l+r)/2;  
    build(l,mid,i*2);  
    build(mid+1,r,i*2+1);  
}  
  
void getline(int i)  
{  
    if(b[i].cnt)b[i].len=pos[b[i].r+1]-pos[b[i].l];  
    else if(b[i].l==b[i].r){
    	b[i].len=0;
    }
    else b[i].len=b[i*2].len+b[i*2+1].len;  
}; 
  
void update(int l,int r,int add,int i)  
{  
    int mid;  
    if(b[i].l==l && b[i].r==r){  
        b[i].cnt+=add;  
        getline(i);return;  
    }  
    mid=(b[i].l+b[i].r)/2;  
    if(r<=mid)update(l,r,add,i*2);  
    else if(l>mid)update(l,r,add,i*2+1);  
    else {  
        update(l,mid,add,i*2);  
        update(mid+1,r,add,i*2+1);  
    }  
    getline(i);  
}  
  
int main()  
{  
    int n,i,j,num,l,r,k=0;  
    double x2,y2,x3,y3,sum;  
    while(scanf("%d",&n)!=EOF && n!=0)  
    {  
        num=0;sum=0;  
        for(i=1;i<=n;i++){  
            scanf("%lf%lf%lf%lf",&x2,&y2,&x3,&y3);  
            num++;  
            s[num].x2=x2;s[num].x3=x3;s[num].y=y2;s[num].f=1;pos[num]=x2;  
            num++;  
            s[num].x2=x2;s[num].x3=x3;s[num].y=y3;s[num].f=-1;pos[num]=x3;  
        }  
        sort(s+1,s+1+num,cmp);  
        sort(pos+1,pos+1+num);  
        m=1;  
        for(i=2;i<=num;i++){  
            if(pos[i]!=pos[i-1]){  
                m++;pos[m]=pos[i];  
            }  
        }  
        build(1,m-1,1);  
        s[0].y=0;  
        for(i=1;i<=num;i++){  
            sum+=b[1].len*(s[i].y-s[i-1].y);  
            l=find(s[i].x2);  
            r=find(s[i].x3)-1;  
            update(l,r,s[i].f,1);  
        }  
        k++;  
        printf("Test case #%d\n",k);  
        printf("Total explored area: %.2f\n\n",sum);  
    }  
    return 0;  
}  




代码三:适用范围广,可以用于其他增减线段的题
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<string>
#include<algorithm>
using namespace std;
#define maxn 250
struct edge{
	double x2,x3,y;
	int f;
}a[maxn];
double num,pos[maxn];
int n,m;
bool cmp(edge a,edge b){
	return a.y<b.y;
}
struct node{
	int l,r,cnt;
	double len;
}b[4*maxn];

void build(int l,int r,int i)
{
	int mid;
	b[i].l=l;b[i].r=r;b[i].cnt=b[i].len=0;
	if(l==r)return;
	mid=(l+r)/2;
	build(l,mid,i*2);
	build(mid+1,r,i*2+1);
}

void update(int l,int r,int add,int i)
{
	int mid;
	if(b[i].cnt!=-1 && b[i].l==l && b[i].r==r){
		b[i].cnt+=add;return;
	}
	if(b[i].cnt!=-1){
		b[i*2].cnt=b[i*2+1].cnt=b[i].cnt;
		b[i].cnt=-1;
	}
	mid=(b[i].l+b[i].r)/2;
	if(r<=mid)update(l,r,add,i*2);
	else if(l>mid)update(l,r,add,i*2+1);
	else{
		update(l,mid,add,i*2);
		update(mid+1,r,add,i*2+1);
	}
}

void question(int l,int r,int i)
{
	int mid;
	if(b[i].l==l && b[i].r==r && b[i].cnt>0){
		num+=pos[b[i].r+1]-pos[b[i].l];return;
	}
	if(b[i].cnt==0)return;
	mid=(b[i].l+b[i].r)/2;
	if(r<=mid)question(l,r,i*2);
	else if(l>mid)question(l,r,i*2+1);
	else {
		question(l,mid,i*2);
		question(mid+1,r,i*2+1);
	}
}

int find(int l,int r,double x)
{
	int mid;
	while(l<=r){
		mid=(l+r)/2;
		if(pos[mid]==x)return mid;
		else if(pos[mid]>x)r=mid-1;
		else l=mid+1;
	}
	if(pos[l]==x)return l;
	else return r;
}

int main()
{
	int i,j,t,t1,t2,num1=0;
	double c,d,e,f,x,y,xx,yy,sum;
	while(scanf("%d",&n)!=EOF && n!=0)
	{
		t=0;
		for(i=1;i<=n;i++){
			scanf("%lf%lf%lf%lf",&x,&y,&xx,&yy);
			pos[i]=x;pos[i+n]=xx;
			t++;
			a[t].y=y;a[t].x2=x;a[t].x3=xx;a[t].f=1;
			t++;
			a[t].y=yy;a[t].x2=x;a[t].x3=xx;a[t].f=-1;
		}
		sort(pos+1,pos+1+2*n);
		m=1;
		for(i=2;i<=2*n;i++){
			if(pos[m]!=pos[i]){
				m++;pos[m]=pos[i];
			}
		}
		build(1,m,1);
		sum=0;
		sort(a+1,a+1+2*n,cmp);
		a[0].y=0;
		for(i=1;i<=2*n;i++){
			num=0;
			question(1,m-1,1);
			sum+=num*(a[i].y-a[i-1].y);
			t1=find(1,m,a[i].x2);
			t2=find(1,m,a[i].x3);
			update(t1,t2-1,a[i].f,1);
		}
		num1++;
		printf("Test case #%d\n",num1);
		printf("Total explored area: %.2f\n",sum);
		printf("\n");
	}
	return 0;
}


你可能感兴趣的:(线段树)