略谈扫描线(真的略谈)
先让我自己学一学吧,之前 xjgg 讲的时候一直没听懂,让我自己先研究一下。2023.11.6 18:33
P5490 【模板】扫描线
前言
20:06 成功理解其基本内涵和代码实现,在艰难的实践和查询资料比对自己代码之后终于自己研究出了一个适应我码风的解法,虽然我这个 stl 套 stl 是巨大无比的常数,不过我认为还是很不错的,而且发现了一个空间上的小优化点,也算是不错了吧。
步入正题
所谓扫描线,就是把二维平面数据拆成一维线段数据进行处理,我们计算面积并显然不能直接暴力拆成点集然后去扫。考虑到这实际上可以将矩形的面积看成一段段小线段的面积,我们只需要维护每段小线段之间的间距乘以其长度即可。
而线段长度可以通过线段树维护,我们只需要排完序以后一条一条加入即可,每次查询并计算此时围成的面积。
但是要注意,我们的线段树不是把它分成原来的 \([l,mid],[mid+1,r]\) 了,这样的话中间的 \([mid,mid+1]\) 这部分的长度就无了,所以我们拆成 \([l,mid],[mid,r]\) 进行处理,不过此时要注意一些不一样的小细节。
具体细节看代码吧,这里讲不清楚。我的代码是基本上完全基于我平时打线段树的代码习惯,没有去模仿别人的码风,怕我搞混了。
\(Code\)
const int N=1e5+5;
struct tree{int len,mark;}tr[N<<3];//离散之后有 N<<1 个点,而线段树开四倍,总共开八倍,一点也不浪费
struct matrix{
int x,y1,y2,mark;
inline bool operator <(const matrix &o)const{return x<o.x;}
}a[N<<1];
int n,tot,len[N<<1];
set<int>ls;
map<int,int>M;
inline void pushup(int root,int l,int r){
if(tr[root].mark)tr[root].len=len[r]-len[l];//因为我们维护是线段,所以这里如果被矩形线覆盖就直接减就行
else if(r-l>1)tr[root].len=tr[root<<1].len+tr[root<<1|1].len;//没有被覆盖就不能那么算,要左右儿子相加
else tr[root].len=0;//这里是不用开到四倍的优化,因为如果我们pushup到这,不特判 r==l+1 的话,我下面没有儿子却还乘二,所要的内存自然又大了一倍
}//因为没有初始值,我更偏向于不build直接update
void update(int root,int l,int r,int x,int y,int v){
if(x<=l&&r<=y){//这里跟普通区间修改一样,符合我个人代码习惯
tr[root].mark+=v;
pushup(root,l,r);
return;
}
int mid=l+r>>1;
if(x<mid)update(root<<1,l,mid,x,y,v);//注意这里是x<mid而不是x<=mid,因为右边区间包括了mid,所以如果被包含也算在右边不需要继续递归了
if(mid<y)update(root<<1|1,mid,r,x,y,v);//是[mid,r]
pushup(root,l,r);
}
ll ans;
int main(){
n=read();
for(int i=1;i<=n;i++){
int x1=read(),y1=read(),x2=read(),y2=read();
a[(i<<1)-1]={x1,y1,y2,1};
a[i<<1]={x2,y1,y2,-1};//1是加入,-1是删除
ls.insert(y1),ls.insert(y2);//不喜欢用unique离散,所以直接用set加map大常离散好了
//对了这里是不需要离散 x 的,x 和 y 是独立的
}
for(int i:ls)M[i]=++tot,len[tot]=i;//估计复杂度都在这里了,两个log,懒得优化了,代码短多好看。
sort(a+1,a+1+(n<<1));//以x为关键字排序
for(int i=1;i<(n<<1);i++){//最后一个不用加了
update(1,1,tot,M[a[i].y1],M[a[i].y2],a[i].mark);//先更新当前线段长
ans+=1ll*(a[i+1].x-a[i].x)*tr[1].len;//间距乘段长就是面积
}
cout<<ans;//撒花~~
return 0;
}
继续去研究扫描线了~ 20:27
P1856 [IOI1998] [USACO5.5] 矩形周长Picture
大水紫,只需要在求面积的基础上把乘段长改为求段长变化量即可,因为 \(x,y\) 等价,所以我们只需要用同一个函数做两遍相加即可。
不过千万要注意对于同一 \(x\) 应该先增后减,不然如果有两个矩形边贴在一起会判定为分开计算周长而不是一个整体,我一开始就错这里!!
\(Code\)
const int N=5003;
set<int>S;
unordered_map<int,int>M;
int n,tot,sa[2][2][N],len[N<<1];
struct matrix{
int x,y1,y2,k;
inline bool operator<(const matrix &w)const{
if(x==w.x)return k>w.k;//特判!!
return x<w.x;
}
}a[N<<1];
struct tree{int len,k;}tr[N<<3];
inline void pushup(int root,int l,int r){
if(tr[root].k)tr[root].len=len[r]-len[l];
else if(r>l+1)tr[root].len=tr[root<<1].len+tr[root<<1|1].len;
else tr[root].len=0;
}
void update(int root,int l,int r,int x,int y,int v){
if(x<=l&&r<=y)return tr[root].k+=v,pushup(root,l,r),void();
int mid=l+r>>1;
if(mid>x)update(root<<1,l,mid,x,y,v);
if(mid<y)update(root<<1|1,mid,r,x,y,v);
pushup(root,l,r);
}
int work(bool oo){
int ans=0;
S.clear(),tot=0,M.clear();
for(int i=1;i<=n;i++){
a[i-1<<1]={sa[oo][0][i],sa[oo^1][0][i],sa[oo^1][1][i],1};
a[i-1<<1|1]={sa[oo][1][i],sa[oo^1][0][i],sa[oo^1][1][i],-1};
S.insert(sa[oo^1][0][i]),S.insert(sa[oo^1][1][i]);
}
for(int i:S)M[i]=++tot,len[tot]=i;
sort(a,a+(n<<1));
memset(tr,0,sizeof(tr));
for(int i=0,las=0;i<(n<<1);i++){
update(1,1,tot,M[a[i].y1],M[a[i].y2],a[i].k);
ans+=abs(tr[1].len-las);las=tr[1].len;
}
return ans;
}
int main(){
n=read();
for(int i=1;i<=n;i++)
sa[0][0][i]=read(),sa[1][0][i]=read(),
sa[0][1][i]=read(),sa[1][1][i]=read();
cout<<work(0)+work(1);
return 0;
}
现在已经 21:12 了,四舍五入我一个晚上就学了一个算法,怪不得说我是废物呢(悲),抓紧时间吧!
AT_abc327_f
刚打的新鲜的扫描线加双指针加线段树,题解之前为了水估值发了。

浙公网安备 33010602011771号