题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1542
解题思路:
Part 1 扫描线相关介绍:
简而言之,按顺序扫过这些个正方形的竖边,然后处理当前竖边在y轴上的投影
每一个矩形都有两条边,入边就对区间覆盖一次,出边就消去一次。(不是全部清除)
如图,两个有重叠的矩形的面积就分成了三部分算。
每次只要算 两条竖边的间距×在y轴投影就OK
Part 2 线段树的构建
线段树维护当前扫过线段在y轴上的投影长度。
由于线段树维护的区间时整形的,所以需要对y的坐标离散化。
本题的难点是线段树区间覆盖要记录次数。
普通的区间覆盖就是区间染色,可以pushup,pushdown实现。
而这题的问题在于区间清0操作你无法判断当前是否应该清空区间数据,因为可能区间被覆盖了多层。
这道题,利用一个标记记录区间被染色的次数用于辅助线段树的建立(不是下放标记),如果标记不为0,证明整段区间都被染色。
区间为0,就从两个儿子传过来,来保证当前节点整个区间没有染色时可以从子节点得到信息。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#define debug(x) printf("----Line%s----\n",#x)
#define ll long long
#define mid int m=l+r>>1
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define tl tree[rt<<1]
#define tr tree[rt<<1|1]
using namespace std;
const int N = 100*2+5;
struct T
{
double x;
double l,r;
bool ru;
bool operator < (T a)const {
return x < a.x;
}
}edge[N];
double date[N];
double tree[N<<2];
int lazy[N<<2];
map<double,int>mp;
void push_up(int rt,int l,int r)
{
if (lazy[rt]) tree[rt] = date[r] - date[l-1];
else if (l==r) tree[rt] = 0;
else tree[rt] = tl+tr;
}
void update(int L,int R,int c,int rt,int l,int r)
if (L<=l && r<=R){
lazy[rt] += c;
push_up(rt,l,r);
//printf("tree[%d]=%.2f\n",rt,tree[rt]);
return ;
}
mid;
if (L<=m) update(L,R,c,lson);
if (R>m) update(L,R,c,rson);
push_up(rt,l,r);//printf("tree[%d]=%.2f\n",rt,tree[rt]);
}
int main()
{
int n,kca=1;
while (~scanf("%d",&n),n){
memset(lazy,0,sizeof lazy);
memset(tree,0,sizeof tree);
double x1,y1,x2,y2;
int tot = 0;
for (int i=1;i<=n;i++){
scanf("%lf %lf %lf %lf",&x1,&y1,&x2,&y2);
edge[tot].x = x1;
edge[tot].l = y1;
edge[tot].r = y2;
edge[tot].ru = true; date[tot] = y1;
edge[++tot].x = x2;
edge[tot].l = y1;
edge[tot].r = y2;
edge[tot].ru = false; date[tot++] = y2;
}
sort(date,date+tot);
int cnt = unique(date,date+tot)-date;
for (int i=0;i<cnt;i++) mp[date[i]] = i;
sort(edge,edge+tot);
double area = 0;
for (int i=0;i<tot-1;i++){
update(mp[edge[i].l]+1,mp[edge[i].r],edge[i].ru?1:-1,1,1,cnt-1);
double xx = edge[i+1].x-edge[i].x;
double yy = tree[1];
area += xx*yy;
//printf("xx=%.2f,yy=%.2f,xx*yy=%.2f\n",xx,yy,xx*yy);
}
printf("Test case #%d\n",kca++);
printf("Total explored area: %.2f\n\n",area);
}
return 0;
}
总结:
区间多次覆盖的线段树用到一个跟之前都不同的标记,用于记录当前节点对应区间被覆盖次数,且这个信息只是针对某一个节点,不会向上向下传递,通过它来辅助线段树维护正确的信息。
吐槽:
啊,已经做了好长一段时间线段树了,可恶,kuangbin的线段树专题还没写完,下次再做线段树估计的好久之后了,下个礼拜弄弄各种字符串的数据结构。