算法模型分析:
·ACM中,扫描线被用来处理矩形相交后面积问题,十分易懂,代码也逻辑性明了,容易懂,类题代码类似,常与线段树搭配使用,时间复杂度低,但如果无法灵活处理线段树,那么学起来也比较艰辛!
·在比赛或者生活中,我们会经常遇到多个矩形叠放后,求并图形的面积,这是个非常明了的问题,一般最初级的思维:暴力求解---矩形切割
(参考: 薛矛 IOI2004国家集训队论文 《解决动态统计问题的两把利刃——剖析线段树与矩形切割》)
·先挑二个矩阵切割放入队列中 ·然后对后面所有的矩形依次与队列中的矩阵比较 ·相交就切割后放入队列 ·不相交直接放入队列 ·比较完后,求队列中所有矩形的面积sum 即为面积并 时间复杂度:对于极端数据,为O(n^3),解题是会TLE的 |
·结合高等数学中积分求面积,将图形沿x轴,dx无限小,将图形切割成一个个连续的矩形,累加每个矩形,得到面积并,x是连续的。
对于编程,我们很难处理这个连续的x,但在这里,我们的对象本身就是一个个现成的矩形,所以我们的x是离散的且仅有2n个;
而我们仅需找到对应x的y值(为x=x0(代表每一矩形的竖边的x)与合并图形的相交的长度),所有竖边的x直线即为我们的扫描线;
一个矩形有两条竖边(左x=x1、右x=x2),对于[x1,x2)之间的扫描线,该矩形与扫描线有交线,长度为矩形的竖边的长度;到了x2,该矩形的面积已经计算好了;
即对应于两个过程,当扫描到x1,左竖边应加入线段树,更新出y的长度,进而求出x1与后继扫描线之间的面积;一直到x2,将其从线段树中删去;
线段树的模板:
·定义树节点:
· 线段左右端点的序号l,r;(处理扫描线问题,我习惯将其视为闭合,且叶节点长度为1即r-l=1,而非点)
·len数组:用于记录每一段中,len[i]记录被覆盖了不小于i次的长度;
这就是要处理的区间问题;
· cover记录整个区间长度被覆盖的次数
这是我们的懒标记,由于问到的是题目中整个区间的覆盖长度,也就是说仅查询节点1的覆盖长度,查询单一,即不用写query函数,而又因为一旦整个长度覆盖,即表示全部被覆盖,所以懒标记不用向下传递;
struct stnode_t{
int l,r;
double len;
int cover;
}ST[Size<<2];
·建树:
非常简单的递归mkSTree(int l,int r,int rt);
·赋值节点rt的l,r端点的序号为l,r;
·r-l==1,为叶节点,直接返回;
·得到左右子区间的分界点序号:mid=(l+r+1)>>1;
·创建左右子线段树:
mkSTree(l,mid,lson);
mkSTree(mid+1,r,rson);
void mkSTree(int l,int r,int rt){
ST[rt].l=l;ST[rt].r=r;ST[rt].len=0;ST[rt].cover=0;
if(r-l==1) return;
int mid=(l+r+1)>>1;
mkSTree(l,mid,lson);
mkSTree(mid,r,rson);
}
·区间更新:
1)为使代码更简洁:所以将增删函数合为更新函数!
2)有两种设计模式:这里我们采取不更改目标区间的写法。
·如果目标区间包含了遍历的区间,那么该节点cover++或者cover--;
·否则 如果目标区间包含了该区间的左部,即更新左区间;
如果目标区间包含了该区间的右部,即更新右区间;
·当前节点数据更新;
void update(double y1,double y2,int rt,int val){//val为1或-1
if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2)
ST[rt].cover+=val;
else{
int mid=(ST[rt].r+ST[rt].l+1)>>1;
if(y1<Y[mid])update(y1,y2,lson,val);
if(y2>Y[mid])update(y1,y2,rson,val);
}
updata(rt);
}
·当前节点数据更新:
1)这是我们对不同的题目需要更改的地方,不同的题目对覆盖次数要求不同
而写法不同,但格式一样!
以求多矩形面积并为例:
·如果当前节点cover>0,则len[1]=区间长度;
·否则(即未被整个区间覆盖)如果不是叶节点,len[1]=左右区间被覆盖的长度之和;
·否则为叶节点,len[1]为0;
void updata(int rt){
if(ST[rt].cover>0)ST[rt].len=Y[ST[rt].r]-Y[ST[rt].l];
else if(ST[rt].r-ST[rt].l==1) ST[rt].len=0;
else ST[rt].len=ST[lson].len+ST[rson].len;
}
其实已经明白了扫描线问题就是线段树的一种特殊应用模板题!
实战应用:
·hdu 1542 Atlantis
题意:有n个矩形,给你左下点和右上点,问合并后的图形的面积;
·就是我们的例子,非常简单,只需将线段树中len类型换为double类型;
/*
*求矩形面积并
*线段树+扫描线
*/
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define lson (rt<<1)
#define rson ((rt<<1)|1)
#define Size 210
using namespace std;
struct stnode_t{
int l,r;
double len;
int cover;
int lb,rb;
}ST[Size<<2];
struct yline{
double x;
double y1,y2;
int flag;
}edge[Size];
double Y[Size];
//建树
void mkSTree(int l,int r,int rt){
ST[rt].l=l;ST[rt].r=r;ST[rt].len=0;ST[rt].cover=0;
if(r-l==1) return ;
int mid=(l+r+1)>>1;
mkSTree(l,mid,lson);
mkSTree(mid,r,rson);
}
//更新当前节点的数据
void updata(int rt){
if(ST[rt].cover>0)ST[rt].len=Y[ST[rt].r]-Y[ST[rt].l];
else if(ST[rt].r-ST[rt].l==1) ST[rt].len=0;
else ST[rt].len=ST[lson].len+ST[rson].len;
}
//区间更新
void update(double y1,double y2,int rt,int val){
if(y1<=Y[ST[rt].l]&&Y[ST[rt].r]<=y2) ST[rt].cover+=val;
else{
int mid=(ST[rt].l+ST[rt].r+1)>>1;
if(y1<Y[mid]) update(y1,y2,lson,val);
if(Y[mid]<y2) update(y1,y2,rson,val);
}
updata(rt);
}
bool cmp(yline &a,yline &b){
if(a.x==b.x) return a.y1<b.y1;
return a.x<b.x;
}
int main(){
int n;int kase=0