扫描线入门学习笔记 (主要讲解代码实现)
思路被很多大佬讲的很清楚,膜拜~
线段树+扫描线(有关扫描线的理解)(图文并茂,思路讲解清楚)
该文离散化后的线段树节点对应的l,r表示区间[x[l],x[r+1]],与本文有不同。
本文为了方便作者的习惯,使线段树节点对应的l,r表示区间[x[l],x[r]],只需注意[l,r]的左儿子为[l,mid],右儿子为[mid,r],且因为叶子节点表示一段区间,所以叶子节点都为[l,l+1]。遇到l==r的节点[l,r],直接return就好。但思路其实并无不同。
本文以模板题(HDU 1542 知若干矩形坐标,求矩形面积并(多组数据))为例(变量意义同上文大佬博客),直接从代码实现讲起,学思路请看上文的大佬博客。
需要一棵维护区间中 未被覆盖/被覆盖 的长度的线段树(数据结构)。
扫描线的线段树实现较传统不同(一开始让我花了很长一段时间去理解。斜体+下划线字体的字请在看完线段树维护的意义后回过头再看一遍,以获得更深的理解):
特殊点:
修改操作对答案有覆盖作用,产生非线性贡献。用传统线段树子树根节点维护子树所有修改信息总和的话,加边很好办,但对于减边操作(给区间加入一次上边,由于上边的d标记为-1,为了方便,称其为“减边”)难以快速处理。——》情况1
查询只有全局查询。 ——》没有区间查询,故不用考虑区间修改后的下传
应对:
修改操作不下传,保证操作可撤回性(这样的话,减边后若当前节点表示的区间不被完整覆盖,可直接由儿子推得贡献)。
只使必要的答案信息(kcnt,len)满足树形性质。
树形性质:子树根节点记录子树的贡献和(但贡献不一定是根的儿子节点贡献的代数和,只是贡献的推得方式满足树形形式(子树总贡献由子树根节点及子树的子树推得))
节点的贡献不考虑其祖先的修改,其祖先的修改会在回溯到这个祖先时考虑——》对应情况1、下文线段树维护的意义。、
做法:
对每个线段树节点[l,r],维护两个量:
cnt:加边时完整覆盖[l,r] 的次数(不算拼凑)。>=0时表示[l,r]被完整覆盖,知sum=该区间的长度;否则该区间未被完整覆盖,但部分可能有覆盖,此时sum由儿子推得。
由于对于每次减边,都有一次加边操作先于其进行,故cnt不会<0。
sum:区间[l,r]内被覆盖的区域的总长度。
同时,对于每个线段树节点,它记录以他为根的子树的贡献和,且该子树不考虑对其根的祖先的修改的影响。其根的祖先的修改的影响会在回溯到这个祖先时考虑。这样若其根的祖先u经历一次减边后它的cnt==0,则可很方便地由u的儿子推出u的sum。
实质:线段树的非叶子节点也能代表一段区间,也会产生贡献,与传统不同(传统线段树,即用线段树维护序列的大部分情况,只有叶子结点有贡献,非叶节点的子树贡献和多为子树的根的子节点的贡献代数和)。总贡献借树形结构求解(由下(子树)向上(祖先)推,查询时只知上就够)而已。
模板题代码请看上文大佬博客。
例题应用:
题目大意:求矩形并的周长
分析:将组成周长的线段单独提出分析,发现分为横、竖边两种情况。若我们从下向上扫描,发现所有同一高度的横边为扫描线在该横边高度加/减边时扫描线覆盖区域总长度的变化量;在相邻两个离散化后的y1,y2之间的竖边总长度为扫描线中覆盖区域与非覆盖区域的分界点的个数*|y2-y1|。
对线段树每个节点[l,r]维护若干变量:
cov:同上题cnt
len:同上题sum
vl:区间左端点l是否被覆盖
vr:右端点是否被覆盖
siz:区间[l,r]的离散化前的长度,同上题
kcnt:区间[l,r]中覆盖区域与非覆盖区域的分界点个数(在考虑当前区间时,区间外的部分视为未被覆盖)
len、cnt的维护同上题,vl,vr,siz的维护很简单,说下kcnt的维护:
若cnt>0,kcnt=2;
否则,kcnt=lson.kcnt+rson.kcnt,然后:
若lson.vr&&rson.vl,kcnt-=2。
在纸上画下图,很容易明白。
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 5 #define qiumid (l+r)>>1 6 #define qiuls (t<<1) 7 #define qiurs ((t<<1)|1) 8 9 using namespace std; 10 11 const int N=5005; 12 13 struct edge{ 14 int x1,x2,y,in; 15 }e[N<<1]; 16 17 struct node{ 18 int cov,vl,vr,kcnt,len,siz; 19 }tre[N<<4]; 20 21 int n,x[N<<1],tx[N<<1],cntx,ecnt,totx; 22 23 long long ans; 24 25 inline int read()//Z 26 { 27 int x=0; 28 bool f=0; 29 char ch=getchar(); 30 while(!isdigit(ch)) 31 f|=ch=='-',ch=getchar(); 32 while(isdigit(ch)) 33 x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 34 return f?-x:x; 35 } 36 37 inline bool cmp(const edge &a,const edge &b) 38 { 39 return a.y<b.y||(a.y==b.y&&a.in>b.in); 40 } 41 42 inline int abs(int a,int b) 43 { 44 return a-b<0?b-a:a-b; 45 } 46 47 void build(int t,int l,int r) 48 { 49 if(l>=r) 50 { 51 cout<<"l==r"<<endl; 52 return; 53 } 54 tre[t].siz=tx[r]-tx[l]; 55 tre[t].kcnt=0; 56 if(l+1==r) 57 return; 58 build(qiuls,l,qiumid); 59 build(qiurs,qiumid,r); 60 } 61 62 inline void updata(int t) 63 { 64 if(tre[t].cov) 65 return; 66 int ls=qiuls,rs=qiurs; 67 tre[t].len=tre[ls].len+tre[rs].len; 68 tre[t].vl=tre[ls].vl;tre[t].vr=tre[rs].vr; 69 tre[t].kcnt=tre[ls].kcnt+tre[rs].kcnt; 70 if(tre[ls].vr&&tre[rs].vl) 71 tre[t].kcnt-=2; 72 } 73 74 void modify(int t,int l,int r,int ll,int rr,int v) 75 { 76 if(l==r) 77 return; 78 if(ll<=l&&r<=rr) 79 { 80 if(!tre[t].cov) 81 { 82 tre[t].cov=1; 83 tre[t].len=tre[t].siz; 84 tre[t].vl=tre[t].vr=1; 85 tre[t].kcnt=2; 86 } 87 else 88 { 89 tre[t].cov+=v; 90 if(!tre[t].cov) 91 updata(t); 92 } 93 return; 94 } 95 if(l+1==r) 96 return; 97 int mid=qiumid; 98 if(ll<=mid) 99 modify(qiuls,l,mid,ll,rr,v); 100 if(rr>=mid) 101 modify(qiurs,mid,r,ll,rr,v); 102 updata(t); 103 } 104 105 int main() 106 { 107 // freopen(".in","r",stdin); 108 // freopen(".out","w",stdout); 109 n=read(); 110 int x1,y1,x2,y2; 111 for(int i=1;i<=n;++i) 112 { 113 x1=read(),y1=read(),x2=read(),y2=read(); 114 e[++ecnt].x1=x1; 115 e[ecnt].x2=x2; 116 e[ecnt].y=y1; 117 e[ecnt].in=1; 118 e[++ecnt].x1=x1; 119 e[ecnt].x2=x2; 120 e[ecnt].y=y2; 121 e[ecnt].in=-1; 122 x[++cntx]=x1;x[++cntx]=x2; 123 } 124 sort(x+1,x+cntx+1); 125 tx[totx=1]=x[1]; 126 for(int i=2;i<=cntx;++i) 127 if(x[i]!=x[i-1]) 128 tx[++totx]=x[i]; 129 build(1,1,totx); 130 sort(e+1,e+1+ecnt,cmp); 131 int lst=0,u,v,lsty=e[1].y; 132 for(int i=1;i<=ecnt;++i) 133 { 134 u=lower_bound(tx+1,tx+totx+1,e[i].x1)-tx; 135 v=lower_bound(tx+1,tx+totx+1,e[i].x2)-tx; 136 ans+=tre[1].kcnt*(e[i].y-lsty); 137 modify(1,1,totx,u,v,e[i].in); 138 ans+=abs(tre[1].len-lst); 139 lst=tre[1].len; 140 lsty=e[i].y; 141 } 142 cout<<ans; 143 return 0; 144 }
启示:
当发现对线段树来说,操作有覆盖性,且加入操作好做,但删除不好做时,便可用上文的线段树的解法(修改操作不下传;节点的贡献不考虑其祖先的修改,其祖先的修改会在回溯到这个祖先时考虑)。若有区间询问,可用标记永久化的思路继续做。
(还是要会灵活运用知识呀)