扫描线思想:扫描线算法
被覆盖1次的区域面积
扫描线一个很经典的例题(HDU-1542-Atlantis):在坐标轴上有若干个矩形,问他们覆盖的面积总和。
因为他们覆盖的面积有重复,于是就用到了神奇的扫描线算法。
扫描线一般就是平行于x轴或者y轴的直线。通过这些直线我们可以把这些矩形覆盖的区域分成若干个子区域分别求面积最后再求和,如下图的区域就被分成了5个子区域。(我是以x轴建的线段树)
要求得这些子区间的面积,重点就在于求每个子区间被覆盖的区间长度和。扫描线向上移动每遇到一条矩形的边,就意味着一段连续的区间会被这个矩形覆盖,或者这段连续的区间不再被这个矩形覆盖。这样就变成了一个区间修改的问题。遇到矩形的下边,这段区间的覆盖次数就加一;遇到矩形的上边,这段区间的覆盖次数就减一,每次统计整段区间上被覆盖过的区间长度。
还有一点是,因为题目中的横坐标值比较大,且是浮点数,所以需要离散化一下。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define mid ((l + r)>>1)
#define chl root<<1
#define chr root<<1|1
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int manx=510;
double tree[manx<<2],X[manx<<1];
int cover[manx];
//tree维护的是1-n中被覆盖的区间长度和
//X存的是所有矩形顶点的横坐标,最后要离散化
//cove维护每个区间被覆盖的次数
struct node
{
double y,lx,rx;
int val;
friend bool operator<(node a,node b)
{
return a.y<b.y;
}
//node(double a=0,double b=0,double c=0,int d=0):y(a),lx(b),rx(c),val(d){}
}line[manx<<1];
//存矩形上下边的信息,每条边会对应一次区间修改
void init()
{
memset(tree,0,sizeof(tree));
memset(cover,0,sizeof(cover));
}
void eval(int root,int l,int r)
{
if(cover[root])
tree[root]=X[r+1]-X[l];
else tree[root]=tree[chl]+tree[chr];
}
void change(int root,int l,int r,int ll,int rr,int val)
{
//这里有些地方写的是l>=ll&&r<=rr的判断条件,他们的区别就在于一个是分的要查询的区间,一个是分的被查询区间(即1-n的区间)
//我分的是要查询的区间(ll-rr),仔细看会发现这两种写法在下面else中的change里的传参是不一样的
if(l==ll&&r==rr)
{
cover[root]+=val;
eval(root,l,r);
return;
}
if(rr<=mid)
change(chl,l,mid,ll,rr,val);
else if(ll>mid)
change(chr,mid+1,r,ll,rr,val);
else
{
change(chl,l,mid,ll,mid,val);
change(chr,mid+1,r,mid+1,rr,val);
}
eval(root,l,r);
}
int main()
{
int n,Cas=0;
double x1,y1,x2,y2;
while(scanf("%d",&n),n)
{
double ans=0;
init();
for(int i=1;i<=n;i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[i*2]=node{y1,x1,x2,1};
X[i*2]=x1;
line[i*2-1]=node{y2,x1,x2,-1};
X[i*2-1]=x2;
}
sort(X+1,X+2*n+1);
sort(line+1,line+2*n+1);
int cnt=unique(X+1,X+2*n+1)-X-1;
for(int i=1;i<2*n;i++)
{
int l=lower_bound(X+1,X+cnt+1,line[i].lx)-X;
int r=lower_bound(X+1,X+cnt+1,line[i].rx)-X-1;
double dy=line[i+1].y-line[i].y;
change(1,1,cnt,l,r,line[i].val);
ans+=tree[1]*dy;
}
printf("Test case #%d\nTotal explored area: %.2f\n\n",++Cas,ans);
}
return 0;
}
被覆盖2次的区域面积
例题:HDU-1255-覆盖的面积
正解(维护被覆盖一次和两次的区间长度):hdu 1255 覆盖的面积
做题的时候遇到的一些问题:
1、为什么不能push_down
比如说,当我们更新小区间的过程中,将大区间的标记下传了,那么到后面我们更新大区间时,代表这个大区间状态的结点的lazy就会变成负数。这时就只能再维护被覆盖一次、两次、三次、、、的区间长度,这样才能从它的两个儿子结点得到当前结点的正确状态,或者将区间修改变成单点修改,但这两种方式时间是不允许的。
2、只维护被覆盖两次的区间长度+lazy懒标记的思路为什么不对:最开始写了一个假的线段树+扫描线算法过了,但是看代码跑出来的时间有点长。最后发现我把它写成了一个暴力的单点修改:当时我只用了一个tree数组维护被覆盖了2次的区间长度,cover数组维护区间段上被覆盖的最少次数,还维了一个lazy数组。后面又改了半天发现这个思路好像实现不了(我觉得是实现不了)。因为cover>=2的时候好说,可以直接计算长度,但是=1的时候去不能像上面的例题一样通过左右子区间求解,因为区间修改中push_down只能操作到我们要找的那个区间,就算push_down lazy的值也可能会遗漏计算被覆盖两次上的区间,除非每次push_down到叶子结点(可能这就是7.63的答案总是测出7.50的结果的原因吧)
所以还是得再开一个tree数组维护被覆盖了1次的区间长度。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define mid ((l + r)>>1)
#define chl root<<1
#define chr root<<1|1
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int manx=11000;
double tree[manx<<2],X[manx<<1],tree2[manx];//lazy[manx],
int cover[manx];
struct node
{
double y,lx,rx;
int val;
friend bool operator<(node a,node b)
{
return a.y<b.y;
}
//node(double a=0,double b=0,double c=0,int d=0):y(a),lx(b),rx(c),val(d){}
}line[manx<<1];
void init()
{
memset(tree,0,sizeof(tree));
memset(cover,0,sizeof(cover));
memset(tree2,0,sizeof(tree2));
}
void eval(int root,int l,int r)
{
if(cover[root])
tree[root]=X[r+1]-X[l];
else if(l==r)
tree[root]=0;
else tree[root]=tree[chl]+tree[chr];
if(cover[root]>=2)
tree2[root]=X[r+1]-X[l];
else if(l==r)
tree2[root]=0;
else if(cover[root]==1)
tree2[root]=tree[chl]+tree[chr];
else
tree2[root]=tree2[chl]+tree2[chr];
}
void change(int root,int l,int r,int ll,int rr,int val)
{
if(l==ll&&r==rr)
{
cover[root]+=val;
eval(root,l,r);
return;
}
if(rr<=mid)
change(chl,l,mid,ll,rr,val);
else if(ll>mid)
change(chr,mid+1,r,ll,rr,val);
else
{
change(chl,l,mid,ll,mid,val);
change(chr,mid+1,r,mid+1,rr,val);
}
eval(root,l,r);
}
int main()
{
int t,n;
double x1,y1,x2,y2;
while(scanf("%d",&t)!=EOF)
{
while(t--)
{
scanf("%d",&n);
double ans=0;
init();
for(int i=1; i<=n; i++)
{
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
line[i*2]=node{y1,x1,x2,1};
X[i*2]=x1;
line[i*2-1]=node{y2,x1,x2,-1};
X[i*2-1]=x2;
}
sort(X+1,X+2*n+1);
sort(line+1,line+2*n+1);
int cnt=unique(X+1,X+2*n+1)-X-1;
//X[cnt+1]=X[cnt];
for(int i=1; i<2*n; i++)
{
int l=lower_bound(X+1,X+cnt+1,line[i].lx)-X;
int r=lower_bound(X+1,X+cnt+1,line[i].rx)-X-1;
double dy=line[i+1].y-line[i].y;
change(1,1,cnt,l,r,line[i].val);
ans+=tree2[1]*dy;
}
printf("%.2f\n",ans);
}
}
return 0;
}
之前还犯的一个数组越界的错,杭电上测出RE了,本来数组开的应该够大。。后面发现是eval函数里面最开始没有排除l== r的情况(即遍历到叶子节点时)导致继续访问叶子结点的“左右儿子”造成tree数组下标越界(因为看学长的模板时还感觉可以不用排除l==r的情况,就没有加)。但数组越界第一次在vj上测得的结果是wa,以前听学长说是因为有野指针。。。所以可能就给tree赋了一个随机数吧
立方体交
和上面求被覆盖2次的区域面积的例题类似
我们可以枚举垂直于x轴的平面,用上面的方法求出这个平面上被覆盖了3次以上的面积,然后乘上x轴上的高度
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<set>
using namespace std;
#define LL long long
//#define int long long
#define ull unsigned long long
#define pii pair<int,int>
#define mid ((l + r)>>1)
#define chl (root<<1)
#define chr (root<<1|1)
#define lowbit(x) ( x&(-x) )
const int manx = 2e3 + 10;
const int manx2 = 4e7 + 10;
const int INF = 2e9;
const int mod = 1e4+7;
int t,n,x1,y1,z1,x2,y2,z2,cou,len,Z[manx];//|z|<=500
LL sum[1010<<2][4];
int cover[1010<<2];
struct node
{
int x,y1,z1,y2,z2;
int val,pos;
friend bool operator<(const node &a,const node &b){
// if(a.x==b.x)return a.val>b.val;
return a.x<b.x;
}
}plane[manx],vs[manx];
struct LINE
{
int y,lz,rz;
int val;
friend bool operator<(const LINE &a,const LINE &b){
return a.y<b.y;
}
}line[manx];
void init(int n)
{
for(int i=1;i<=4*n;i++){
cover[i]=0;
for(int j=1;j<=3;j++)
sum[i][j]=0;
}
}
void eval(int root,int l,int r)
{
sum[root][1]=sum[root][2]=sum[root][3]=0;
if(l==r){
for(int i=1;i<=cover[root];i++)
sum[root][i]=Z[r+1]-Z[l];
return;
}
if(cover[root]>=3)
sum[root][1]=sum[root][2]=sum[root][3]=Z[r+1]-Z[l];
else if(cover[root]==2){
sum[root][3]=sum[chl][1]+sum[chr][1];
sum[root][1]=sum[root][2]=Z[r+1]-Z[l];
}
else if(cover[root]==1){
sum[root][3]=sum[chl][2]+sum[chr][2];
sum[root][2]=sum[chl][1]+sum[chr][1];
sum[root][1]=Z[r+1]-Z[l];
}
else{
for(int i=1;i<=3;i++)
sum[root][i]=sum[chl][i]+sum[chr][i];
}
}
void change(int root,int l,int r,int ll,int rr,int val)
{
if(l==ll&&r==rr){
cover[root]+=val;
eval(root,l,r);
return;
}
if(rr<=mid)
change(chl,l,mid,ll,rr,val);
else if(ll>mid)
change(chr,mid+1,r,ll,rr,val);
else{
change(chl,l,mid,ll,mid,val);
change(chr,mid+1,r,mid+1,rr,val);
}
eval(root,l,r);
}
int main()
{
int Cas=0;
scanf("%d",&t);
while(t--){
scanf("%d",&n);
len=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d%d%d%d",&x1,&y1,&z1,&x2,&y2,&z2);
plane[i*2-1]=node{x1,y1,z1,y2,z2,1,i};
plane[i*2]=node{x2,y1,z1,y2,z2,-1,i};
vs[i]=node{-1,y1,z1,y2,z2,0,-1};
}
sort(plane+1,plane+2*n+1);
LL ans=0,temp;
for(int i=1;i<2*n;i++){
temp=0;
vs[plane[i].pos].val+=plane[i].val;
cou=0;
for(int j=1;j<=n;j++){
if(vs[j].val){
line[++cou]=LINE{vs[j].y1,vs[j].z1,vs[j].z2,1};
Z[cou]=vs[j].z1;
line[++cou]=LINE{vs[j].y2,vs[j].z1,vs[j].z2,-1};
Z[cou]=vs[j].z2;
}
}
sort(line+1,line+cou+1);
sort(Z+1,Z+cou+1);
len=unique(Z+1,Z+cou+1)-Z-1;
for(int j=1;j<cou;j++){
int l=lower_bound(Z+1,Z+len+1,line[j].lz)-Z;
int r=lower_bound(Z+1,Z+len+1,line[j].rz)-Z-1;
change(1,1,len,l,r,line[j].val);
LL dy=line[j+1].y-line[j].y;
temp+=dy*sum[1][3];
}
LL dx=plane[i+1].x-plane[i].x;
ans+=dx*temp;
init(len);
}
printf("Case %d: %lld\n",++Cas,ans);
}
return 0;
}
/*
6
0 0 0 5 5 5
3 3 3 5 5 5
3 3 3 5 5 5
0 0 0 5 5 5
3 3 3 5 5 5
3 3 3 5 5 5
*/