Atlantis HDU - 1542
原题链接
考察:线段树(扫描线)
思路:
其实是扫描线入门题.(然后就看了很久才略懂)
本题思路还是直接看大佬博客吧GO
想解释的只有几个问题.
- 为什么结构体如此定义?
struct Node{
int l,r,cnt;//实际代表含义是[l,r+1]区间
double len;//cnt是指该区间被覆盖次数 len是该区间有效长度.
}tr[N<<3];
为什么不用普通线段树一样,l,r表示区间端点呢?这里就涉及了modify函数
void modify(int u,int l,int r,int w)//修改[l,r]区间
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].cnt+=w;
push_up(u);
return;
}
int mid = tr[u].l+tr[u].r>>1;
if(mid>=l) modify(u<<1,l,r,w);
if(mid<r) modify(u<<1|1,l,r,w);
push_up(u);
}
可以发现这个线段树是不用懒标记的.push_down是将父节点信息传递给子节点.普通线段树在两处使用push_down:
- query函数里,本题每次都是询问整个区间,所以没必要push_down
- modify函数里,当当前结点u的区间不能被[l,r]完全覆盖时.
我们之所以需要push_down,是因为懒标记的区间每次询问或者修改可能不同,所以需要将懒标记分裂开.这里每次询问的区间是对称的,分类讨论的话就是:
- 当前区间cnt > 0,我们需要让部分区间--,cnt = 0.但此操作前这个部分区间覆盖次数>1,所以即便--后还是要计算整个区间.
- 当前区间cnt = 0,此时有操作和没有一样.
在modify函数里,假设,总长度是[1,4]要更新的区间是[1,3].根据线段树会裂开为[1,2]和[3,3].因为[3,3]的len==0,此时只能计算到[1,2]的有效长度,[2,3]就被忽略了.
&emso; 那么怎么计算有效长度呢?定义yL代表yL~yL+1的区间.那么在修改时,只需要修改[l,r-1]区间.在push_up时,计算len需要L[r+1]-L[l].这样可以有效解决[1,3]未记录的问题.
- 为什么push_up这么写
void push_up(int u)
{
if(tr[u].cnt) tr[u].len = rget(tr[u].r+1)-rget(tr[u].l);
else if(tr[u].l!=tr[u].r) tr[u].len = tr[u<<1].len+tr[u<<1|1].len;
else tr[u].len = 0;
}
可以发现只要叶子结点cnt有值就会被更新.如果没有特判防止数组超界.当cnt = 0且为叶子节点时一定要重新赋值0.
Code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 10010;
struct Node{
int l,r,cnt;
double len;
}tr[N<<3];
struct Segment{
double x,y1,y2;
int w;
bool operator<(const Segment& s){
return this->x<s.x;
}
}seg[N<<1];
int n,kcase;
vector<double> alls;
int get(double x)
{
return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
double rget(int x)
{
return alls[x-1];
}
void build(int u,int l,int r)
{
tr[u].l = l,tr[u].r = r;
tr[u].len = 0,tr[u].cnt = 0;
if(l==r) return;
int mid = l+r>>1;
build(u<<1,l,mid); build(u<<1|1,mid+1,r);
}
void push_up(int u)
{
if(tr[u].cnt) tr[u].len = rget(tr[u].r+1)-rget(tr[u].l);
else if(tr[u].l!=tr[u].r) tr[u].len = tr[u<<1].len+tr[u<<1|1].len;
else tr[u].len = 0;
}
void modify(int u,int l,int r,int w)//修改[l,r]区间
{
if(tr[u].l>=l&&tr[u].r<=r)
{
tr[u].cnt+=w;
push_up(u);//if cnt 变成 0,说明此区间的子区间len = 0,最终为0
return;
}
int mid = tr[u].l+tr[u].r>>1;
if(mid>=l) modify(u<<1,l,r,w);
if(mid<r) modify(u<<1|1,l,r,w);
push_up(u);
}
int main()
{
while(scanf("%d",&n)!=EOF&&n)
{
alls.clear();
for(int i=1,j=0;i<=n;i++)
{
double x1,y1,x2,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
seg[++j] = {x1,y1,y2,1};
seg[++j] = {x2,y1,y2,-1};
alls.push_back(y1),alls.push_back(y2);
}
seg[0].x = seg[1].x; seg[0].w = 0;
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
sort(seg+1,seg+2*n+1);//2*n个线段
double res = 0;
build(1,1,2*n);
for(int i=1;i<=2*n;i++)
{
double d = seg[i].x-seg[i-1].x;
res+=tr[1].len*d;
modify(1,get(seg[i].y1),get(seg[i].y2)-1,seg[i].w);
}
printf("Test case #%d\n",++kcase);
printf("Total explored area: %.2lf\n\n",res);
}
return 0;
}