求矩形面积并(hdu1542)
我们可以在y轴上用扫描线,如果是矩形的下边就让该段区间+1,如果是上边就让区间-1。那么维护x轴上的区间操作就可以用线段树了。对于答案统计,就是后一条扫描线与当前扫描线的y坐标的差值乘上当前区间下覆盖的长度。
几个注意点
- 坐标有浮点数,范围也比较大,需要离散化处理(可以用unique或者map或者直接for循环去重)。
- 线段树上每个节点维护的区间【l,r】对应坐标轴上的【l,r+1】。可以考虑两个区间【1,2】和【2,3】,如果都是闭区间,覆盖数cover就是2,而实际上这是两个连续区间,cover应该是1,所以用左闭右开来存(很多区间题都要化闭为开)。
- 不需要lazy标记,跟维护sum,max不一样,lazy没法下放。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 205;
double x[N], len[N<<2];
int cover[N<<2];
struct node
{
double l, r, y;
int flag;
friend bool operator < (node a,node b) //扫描线按y轴坐标排序后,从小到大依次扫
{
return a.y < b.y;
}
}s[N];
void pushup(int p,int l,int r)
{
if(cover[p]) len[p] = x[r+1] - x[l]; //如果当前区间被线段覆盖,长度=区间长度
else if(l==r) len[p] = 0; //叶子结点
else len[p] = len[p<<1] + len[p<<1|1]; //儿子结点信息向上更新
}
void update(int p,int l,int r,int x,int y,int val)
{
if(r<x||l>y) return;
if(x<=l&&y>=r)
{
cover[p] += val;
pushup(p,l,r); //不用lazy标记
return;
}
int mid = (l+r) >> 1;
update(p<<1,l,mid,x,y,val);
update(p<<1|1,mid+1,r,x,y,val);
pushup(p,l,r);
}
int main()
{
int n, kas = 0;
while(~scanf("%d",&n)&&n)
{
double x1, y1, x2, y2;
int m = 0, k = 1;
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
x[m] = x1; //存储横坐标去重
s[m++] = {x1,x2,y1,1}; //存储扫描线
x[m] = x2;
s[m++] = {x1,x2,y2,-1};
}
sort(x,x+m);
sort(s,s+m);
for(int i=1;i<m;i++) //离散化处理
{
if(x[i]!=x[i-1]) x[k++] = x[i];
}
memset(cover,0,sizeof(cover));
memset(len,0,sizeof(len));
double ans = 0;
for(int i=0;i<m-1;i++)
{
int l = lower_bound(x,x+k,s[i].l) - x;
int r = lower_bound(x,x+k,s[i].r) - x;
update(1,0,k-1,l,r-1,s[i].flag); //维护的是[l,r-1]
ans += len[1]*(s[i+1].y-s[i].y); //统计答案 len[1]就是当前整个区间被线段覆盖的长度 乘以两个扫描线的高度差
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",++kas,ans);
}
return 0;
}
求矩形周长并(hdu1828)
一种简单的方法就是x轴和y轴分别用扫描线做一次。但是其实可以只扫一维即可。
整体思想和求面积相似。但相较于面积,我们用线段树多维护三个信息,num:该区间上线段端点个数 ld:该区间左端点是否被覆盖 rd:该区间右端点是否被覆盖。对于答案统计,我们分成横向和竖向两部分。
- 横向:这次区间被覆盖的长度与上次覆盖的长度差的绝对值。
- 竖向:区间上线段端点数乘上下条扫描线与这条的高度差。端点数就是num,就相当于竖着的边数。ld和rd的作用就是用来更新num(可以考虑两段区间[1,2]和[3,4],本来合并后端点数num是4,但是两个区间是连续的,所以num-2。
几个注意点
- 数据比较小,而且都是整数,所以可以不用离散化。
- 同样不需要lazy下放,区间左闭右开。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 2e4 + 5;
struct node
{
int l, r, y, flag;
friend bool operator < (node a,node b)
{
return a.y < b.y;
}
}s[N];
int cover[N<<2], len[N<<2], num[N<<2], x[N];
bool ld[N<<2], rd[N<<2];
void pushup(int p,int l,int r)
{
if(cover[p]) //整段区间被覆盖
{
ld[p] = rd[p] = 1;
len[p] = x[r+1] - x[l];
num[p] = 2;
}
else if(l==r) //叶子结点
{
len[p] = num[p] = ld[p] = rd[p] = 0;
}
else
{
ld[p] = ld[p<<1]; //左端点由左儿子更新
rd[p] = rd[p<<1|1]; //右端点由右儿子更新
len[p] = len[p<<1] + len[p<<1|1];
num[p] = num[p<<1] + num[p<<1|1];
if(ld[p<<1|1]&&rd[p<<1]) num[p] -= 2; //两个区间合并成连续区间的情况,端点-2
}
}
void update(int p,int l,int r,int x,int y,int val)
{
if(r<x||l>y) return;
if(x<=l&&y>=r)
{
cover[p] += val;
pushup(p,l,r);
return;
}
int mid = (l+r) >> 1;
update(p<<1,l,mid,x,y,val);
update(p<<1|1,mid+1,r,x,y,val);
pushup(p,l,r);
}
int main()
{
int n;
while(~scanf("%d",&n))
{
int x1, y1, x2, y2, m = 0, k = 1;
for(int i=0;i<n;i++)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x[m] = x1;
s[m++] = {x1,x2,y1,1};
x[m] = x2;
s[m++] = {x1,x2,y2,-1};
}
sort(x,x+m);
sort(s,s+m);
for(int i=1;i<m;i++)
{
if(x[i]!=x[i-1]) x[k++] = x[i]; //可以不离散化
}
for(int i=0;i<k;i++)
{
len[i] = cover[i] = rd[i] = ld[i] = num[i] = 0; //memset
}
int ans = 0, last = 0;
for(int i=0;i<m;i++)
{
int l = lower_bound(x,x+k,s[i].l) - x;
int r = lower_bound(x,x+k,s[i].r) - x;
update(1,0,k-1,l,r-1,s[i].flag);
ans += num[1] * (s[i+1].y - s[i].y); //竖线答案
ans += abs(len[1]-last); //横线答案
last = len[1];
}
printf("%d\n",ans);
}
return 0;
}
总结
对于这种几何题,还是画图更好理解,画几个矩形,模拟一下扫描线的移动应该就很容易,最后要注意线段树的空间别开小了,不然会wa的很迷。