以前一直没仔细学一波扫描线,最近终于补了一下。
算法描述
大致的思想:将坐标的一维离散化,然后建立线段树,维护当前被覆盖的线段长度,然后将另一维排序,按顺序插入线段树。对于一个矩形的上底边和下底边,(如果从下往上扫描)则对下底边作+1标记,上底边作-1标记。用一个结构体存储一条边的信息,包括L:线段左端点,R:线段右端点,h:线段的高度,d:线段的上下底边标记。线段树的一个细节是,每个节点代表的是第几段线段,而不是某个线段的端点,所以线段[L,R]代表的是第L段线段到第R段线段,
其真实长度=v[R+1]-v[L];(其中v数组是离散化数组) 每次两线段之间的面积就是两线段高度差*x轴方向覆盖的线段长度。
代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
#define ll long long int
#define INF 0x3f3f3f3f
int n,m;
struct node{
double l,r,h;
int d;
bool operator < (const node& rid)const {
return h < rid.h;
}
};
node a[maxn<<1];
vector<double>v;
int tot;
int cnt[maxn<<2];
double sum[maxn<<2];
void pushup(int x,int l,int r)
{
if(cnt[x])sum[x]=v[r+1]-v[l];
else if(l==r)sum[x]=0;
else sum[x]=sum[x<<1]+sum[x<<1|1];
}
void update(int x,int l,int r,int lp,int rp,int s){
if(lp<=l&&r<=rp){
cnt[x]+=s;
pushup(x,l,r);
return ;
}
int mid=(l+r)>>1;
if(lp<=mid)update(x<<1,l,mid,lp,rp,s);
if(mid+1<=rp)update(x<<1|1,mid+1,r,lp,rp,s);
pushup(x,l,r);
}
int main()
{
scanf("%d",&n);
v.push_back(-1.0*INF);///保证后来插入的数字最小id为1
for(int i=1;i<=n;i++){
double x1,x2,y1,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
a[++tot]={x1,x2,y1,1};
a[++tot]={x1,x2,y2,-1};
v.push_back(x1);
v.push_back(x2);
}
sort(v.begin(),v.end());
sort(a+1,a+tot+1);
int len=unique(v.begin(),v.end())-v.begin();
double ans=0;
for(int i=1;i<=tot;i++){
int xx=lower_bound(v.begin(),v.begin()+len,a[i].l)-v.begin();
int yy=lower_bound(v.begin(),v.begin()+len,a[i].r)-v.begin();
yy--;
int dd=a[i].d;
if(xx<=yy)update(1,1,len-1,xx,yy,dd);
ans+=sum[1]*(a[i+1].h-a[i].h);///因为最后一条上边延被减去 所以实际上底边为0 所以即使a[i+1]不存在 也不会导致问题
}
printf("%lf\n",ans);
return 0;
}
HDU1542 Atlantis
模板题,给定N个矩形,求覆盖的总面积。注意格式,每次输出之后要跟一个换行。
#include<bits/stdc++.h>
using namespace std;
#define maxn 205
#define ll long long int
#define INF 0x3f3f3f3f
int n;
struct node{
double l,r,h;
int d;
bool operator < (const node &rid)const{
return h < rid.h;
}
}a[maxn<<1];
int tot;
vector<double>v;
int cnt[maxn<<2];
double sum[maxn<<2];
void pushup(int x,int l,int r){
if(cnt[x])sum[x]=v[r+1]-v[l];
else if(l==r)sum[x]=0;
else sum[x]=sum[x<<1]+sum[x<<1|1];
}
void update(int x,int l,int r,int lp,int rp,int s)
{
if(lp<=l&&r<=rp){
cnt[x]+=s;
pushup(x,l,r);
return ;
}
int mid=(l+r)>>1;
if(lp<=mid)update(x<<1,l,mid,lp,rp,s);
if(mid+1<=rp)update(x<<1|1,mid+1,r,lp,rp,s);
pushup(x,l,r);
}
int main()
{
int kase=0;
while(~scanf("%d",&n)){
if(n==0)break;
tot=0;
v.clear();
v.push_back(-1.0*INF);
memset(cnt,0,sizeof(cnt));
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++){
double x1,x2,y1,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
a[++tot]={x1,x2,y1,1};
a[++tot]={x1,x2,y2,-1};
v.push_back(x1);
v.push_back(x2);
}
sort(v.begin(),v.end());
sort(a+1,a+tot+1);
int len=unique(v.begin(),v.end())-v.begin();
double ans=0;
for(int i=1;i<=tot;i++){
int xx=lower_bound(v.begin(),v.begin()+len,a[i].l)-v.begin();
int yy=lower_bound(v.begin(),v.begin()+len,a[i].r)-v.begin();
yy--;
int dd=a[i].d;
update(1,1,len-1,xx,yy,dd);
ans+=sum[1]*(a[i+1].h-a[i].h);
}
printf("Test case #%d\n",++kase);
printf("Total explored area: %.2lf\n\n",ans);
}
return 0;
}
HDU 1255 覆盖的面积
题意如图:
这题的难点在于,覆盖两次,不能简单理解为之前的pushup中的cnt[x]>=2即可,因为即使我这次该区间只被覆盖一次,但是由于底下的小区间未被更新,之前就存在的被覆盖一次的现在会变为两次,所以如果现在该区间被覆盖一次之后,sum[x][1]=sum[x<<1][1]+sum[x<<1|1][1]+sum[x<<1][0]+sum[x<<1|1][0];其中sum[x][1]表示被覆盖两次及其以上的区间的长度,sum[x][0]表示被覆盖一次的区间的长度。
然后对于sum[x][0]的更新也要仔细讨论。详细见代码。
///覆盖两次以上的面积
#include<bits/stdc++.h>
using namespace std;
#define maxn 2005
#define ll long long int
#define INF 0x3f3f3f3f
int n,t;
struct node{
double l,r,h;
int d;
bool operator < (const node &rid)const{
return h < rid.h;
}
}a[maxn<<1];
int tot;
vector<double>v;
int cnt[maxn<<2];
double sum[maxn<<2][2];
void pushup(int x,int l,int r){
if(cnt[x]>=2){
sum[x][1]=v[r+1]-v[l];
sum[x][0]=0;
}
else {
if(cnt[x]){
sum[x][1]=sum[x<<1][0]+sum[x<<1|1][0]+sum[x<<1][1]+sum[x<<1|1][1];
sum[x][0]=v[r+1]-v[l]-sum[x][1];
}
else {
if(l==r)sum[x][1]=sum[x][0]=0;
else {
sum[x][1]=sum[x<<1][1]+sum[x<<1|1][1];
sum[x][0]=sum[x<<1][0]+sum[x<<1|1][0];
}
}
}
}
void update(int x,int l,int r,int lp,int rp,int s){
if(lp<=l&&r<=rp){
cnt[x]+=s;
pushup(x,l,r);
return ;
}
int mid=(l+r)>>1;
if(lp<=mid)update(x<<1,l,mid,lp,rp,s);
if(mid+1<=rp)update(x<<1|1,mid+1,r,lp,rp,s);
pushup(x,l,r);
}
int main()
{
scanf("%d",&t);
while(t--){
tot=0;
v.clear();
v.push_back(-1.0*INF);
memset(cnt,0,sizeof(cnt));
memset(sum,0,sizeof(sum));
scanf("%d",&n);
for(int i=1;i<=n;i++){
double x1,x2,y1,y2;
scanf("%lf%lf%lf%lf",&x1,&y1,&x2,&y2);
a[++tot]={x1,x2,y1,1};
a[++tot]={x1,x2,y2,-1};
v.push_back(x1);
v.push_back(x2);
}
sort(v.begin(),v.end());
sort(a+1,a+tot+1);
int len=unique(v.begin(),v.end())-v.begin();
double ans=0;
for(int i=1;i<=tot;i++){
int xx=lower_bound(v.begin(),v.begin()+len,a[i].l)-v.begin();
int yy=lower_bound(v.begin(),v.begin()+len,a[i].r)-v.begin();
yy--;
int dd=a[i].d;
update(1,1,len-1,xx,yy,dd);
ans+=sum[1][1]*(a[i+1].h-a[i].h);
//printf("%lf\n",ans);
}
printf("%.2lf\n",ans);
}
return 0;
}