POJ1151 Atlantis 解题报告(朴素算法+线段树解法)

POJ1151 Atlantis 0x40「数据结构进阶」例题

描述

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.

输入

The input 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.

输出

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.

样例输入

2
10 10 20 20
15 15 25 25.5
0

样例输出

Test case #1
Total explored area: 180.00

解题思路

参考《算法竞赛进阶指南》李煜东.P212。采用离散化的思想,对y坐标离散化,将每个矩形分成两个竖线,(x1 , y1 , y2 ,1) 和 (x2 , y1 , y2 , -1)于是我们就可以通过从左到右对竖线进行扫描,求出竖线上的高的同时求出与上一条线距离x即可得到答案ans += h * x。

代码示例:
#include<cstdio>
#include<algorithm>
#include<map>
#include<cstring>
using namespace std;
const int MAXN = 150;
int n,cnt = 0;
double xx[MAXN*2],yy[MAXN*2];
int c[MAXN*2];  //用来记录某个位置是否被覆盖
map<double,int> val;
struct Line{
    double x1,y1,y2;
    int p;
    Line(){}
    Line(double x1,double y1,double y2,int p):x1(x1),y1(y1),y2(y2),p(p){}
    bool operator < (const Line B) const{
        return x1 < B.x1;
    }
}lines[MAXN*2];

double solve(){
    sort(yy+1,yy+2*n+1);    //要排序去重之后才能离散化,赋予每个点一个编号
    sort(lines+1,lines+2*n+1);  //lines也要排序,这样可以从左到右依次进行操作
    int yl = unique(yy+1,yy+2*n+1) - yy - 1;//离散化y坐标
    for(int i = 1;i <= yl;i++)
       val[yy[i]] = i;           //利用map哈希表来将y坐标离散化为整数
    double ans = 0;             //用来存储总面积
    for(int i = 1;i <= 2*n;i++){
        /*
        因为我们是按照竖线扫描,再用宽度*高得出该段面积,所以我们需要先求出高len。
        而宽即两个竖线的x坐标相减,即lines[i+1].x1 - lines[i].x1。
        */
        double y1 = lines[i].y1,y2 = lines[i].y2;
        for(int j = val[y1];j < val[y2];j++) c[j] += lines[i].p;
        double len = 0;
        for(int j = 1;j < yl;j++) 
            if(c[j]) len += yy[j+1]-yy[j];
        ans += (lines[i+1].x1 - lines[i].x1)*len;
    }
    return ans;
}
int main(){
    while(scanf("%d",&n) && n != 0){
        for(int i = 1;i <= n;i++){
            scanf("%lf%lf%lf%lf",&xx[i],&yy[i],&xx[i+n],&yy[i+n]);
            //将一个矩形分为两条竖线,存放在lines内
            lines[i] = Line(xx[i],yy[i],yy[i+n],1);
            lines[i+n] = Line(xx[i+n],yy[i],yy[i+n],-1);
        }
        double ans = solve();
        printf("Test case #%d\n",++cnt);
        printf("Total explored area: %.2f\n\n",ans);
    }
    return 0;
}

线段树维护扫描线

上面的做法有可优化的地方,由于每次都要计算该扫描线的覆盖长度,所以每次都要遍历该扫描线上的y1-y2,时间复杂度为O(N)。但是我们可以利用线段树区间修改技术,将扫描线的覆盖次数统计、覆盖长度查询改成对线段树的区间修改、区间查询操作。于是时间复杂度就降为O(log N)。实际上由于我们每次查询都是查询当前扫描线上的高,即整个区间,所以查询实际上是O(1)。
具体做法是,我们之前的离散化不变,解题思想不变,变化的是我们用线段树来维护扫描线上的高的累计h,详情见代码注释。

代码示例:


#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
const int MAXN = 150;
int n,cnt = 0;
double xx[MAXN*2],yy[MAXN*2];
map<double,int> val;
int c[MAXN*2]; //用来记录某个位置是否被覆盖
struct Line{
double x1,y1,y2;
int p;
Line(){}
Line(double x1,double y1,double y2,int p):x1(x1),y1(y1),y2(y2),p(p){}
bool operator < (const Line B) const{
return x1 < B.x1;
}
}lines[MAXN*2];
struct SegmentTree{
//l,r,cnt分别代表左右边界、覆盖次数,h为当前区间内高的和
int l,r,cnt;
double h;
#define cnt(x) t[x].cnt
#define l(x) t[x].l
#define r(x) t[x].r
#define h(x) t[x].h
}t[MAXN*4];
void build(int p,int l,int r){
//建树,由于本题初始值都为零,故可简略写
l(p) = l,r(p) = r,h(p) = 0,cnt(p) = 0;
if(l == r) return;
int mid = (l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int v){
if(l <= l(p) && r(p) <= r){
//需要根据cnt标记来得出高
cnt(p) += v;
if(cnt(p)) h(p) = yy[r(p)+1] - yy[l(p)];//是l(p)而不是l
else if(l(p) == r(p)) h(p) = 0;
else h(p) = h(p*2) + h(p*2+1);
return;
}
int mid = (l(p) + r(p))/2;
if(l <= mid) change(p*2,l,r,v);
if(r > mid) change(p*2+1,l,r,v);
//这里需要通过子节点更新父节点
if(cnt(p)) h(p) = yy[r(p)+1] - yy[l(p)];
else if(l(p) == r(p)) h(p) = 0;
else h(p) = h(p*2) + h(p*2+1);
}
double solve(){
double ans = 0;
sort(lines+1,lines+2*n+1);
sort(yy+1,yy+2*n+1);
int yl = unique(yy+1,yy+2*n+1) - yy - 1;
for(int i = 1;i <= yl;i++) val[yy[i]] = i;
build(1,1,yl);
for(int i = 1;i < 2*n;i++){
double y1 = lines[i].y1,y2 = lines[i].y2;
change(1,val[y1],val[y2]-1,lines[i].p);
ans += t[1].h*(lines[i+1].x1 - lines[i].x1);
}
return ans;
}
int main(){
while(scanf("%d",&n) && n != 0){
for(int i = 1;i <= n;i++){
scanf("%lf%lf%lf%lf",&xx[i],&yy[i],&xx[i+n],&yy[i+n]);
//将一个矩形分为两条竖线,存放在lines内
lines[i] = Line(xx[i],yy[i],yy[i+n],1);
lines[i+n] = Line(xx[i+n],yy[i],yy[i+n],-1);
}
double ans = solve();
printf("Test case #%d\n",++cnt);
printf("Total explored area: %.2f\n\n",ans);
}
return 0;
}

参考书目:

  • 《算法竞赛进阶指南》李煜东.P212
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 程序猿惹谁了 设计师:白松林 返回首页