扫描线——~~原理分析与代码块(指针线段树)~~——从入门到放弃

Posted 咕咕坤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了扫描线——~~原理分析与代码块(指针线段树)~~——从入门到放弃相关的知识,希望对你有一定的参考价值。

这里是一级标题

分割线下面是我原准备发的博客,然而事实是:

大概一年以前 通过书籍理解了扫描线的基本原理
5-19 花一小时抄了oi-wiki的代码,疑惑于长还是宽,由于把数组改成指针,过不了样例
5-20 又花了一小时抄了一遍,改了几个bug,知道了区间维护要求build[M,R],过不了样例
5-21 花了三小时理解代码、看其他的博客、重构代码、写博客梳理代码、过了样例,后全WA,手造样例WA、再次修改原理、AC手造样例,再次全WA,然后放弃

2021-5-20 && 2021-5-21,这两个爱你爱你我爱你的日子就在一点也不可爱的扫描线中度过了,无果,寻病终,最后得出结论:我只适合背代码。

所以代码还是错的,我还是不会。

哪个好心人给我讲讲原理和细节或者帮着看一下最终的代码/kl/kl/kl/kl

QAQ

--------------------分割线---------------------

这里是正文的一级标题

OI-wiki上讲的是线段树维护长,给出的代码却是维护的宽,误导了我好久。

我的做法是线段树维护长,用p维护给出的所有横向边(也就是矩形的长)。

扫描线从下往上扫,每个矩形下面的长是这个矩形加入的线段,遇到就加入线段树,上面的长是这个矩形退出的线段,遇到就退出线段树,上/下用flg表示,其值为0/1那么加入和退出的操作就等价于+flg

把所有的线段处理好后就无所谓属于哪个矩形了,也就是线段独立存在了,这时候我们按纵坐标给其排序,从下往上逐条处理即可。

获得排序后,此时每条线段在哪个纵坐标上也就没用了,因为我们维护的是扫描线,是一条二维的线。

所有矩形的面积并也就被分为了一个个单独横条,横条的宽度即为相邻两条线段之间的距离p[i] - p[i - 1]

核心操作就是用线段树rot维护区间[min_x, max_x]上的线段长度,然后乘上长条宽度累计入答案。

线段

维护纵坐标在y上的一条线段[x1, x2]

bool cmp(line a, line b){
  if(a.y == b.y)  return a.flg < b.flg;//注意先出后进
  return a.y < b.y;
}

struct line{
  int y, x1, x2, flg;
}p[maxn * 2];

线段树

节点维护的信息是[l, r]这个区间里当前存在的长度。

struct node{
  int l, r, lazy, sum;
  node *ls, *rs;
}Mem[maxn * 4], *pool = Mem;

离散化:

所有的横坐标放进num[]里然后sort,用[1, n * 2]作为线段树的定义域,根据num[]访问到真正的坐标值,放进nodel和r作为真正的定义域,也就是维护的区间。

离散化就决定了在建树的时候左右孩子的递归就要是这样的:

M = L + R >> 1;
u->ls = build(L, M);
u->rs = build(M, R);//而不是build(M + 1, R)
u->pushup(); 

这样才能保证num[M]num[M + 1]这段区间不被漏掉。

注意到这个lazy是说区间[num[l], num[r]]这里面的线段出现了几次,因为这个区间可能被多条矩形的下边都加入了线段树,退出的时候每次只能退出一条,而只有当所有矩形的上边都退出了时候,这段区间的线段才真正不存在了,不然就是一直要算入答案的。

事实上,lazy只在当前用到的线段树的最下面一层节点(一定要是表示num[i]~num[i+1]这么基本的节点)才有用,其余节点是没有出现次数这一说的,只用维护sum就好了。

所以非叶节点的lazy = 0, sum > 0的情况是合法的。

这也决定了pushdown函数是不必要的。注意我们用的是EqualRange()而不是InRange()也就是说懒标记在这里并不适用,所有的基本线段都要维护。

现在我们来看看upd(x1, x2, flg)的操作,这个操作的对象是线段。

inline bool EqualRange(const int L, const int R){
  return (L == l) && (r == R);
}
inline bool OutofRange(const int L, const int R){
  return (L >= r) || (l >= R);
}
inline void maketag(const int flg){
  lazy += flg;
  if(lazy <= 0)  sum = 0;
  if(lazy > 0)  sum = r - l;
 }
inline void pushup(){
		sum = 0;
		if(ls != NULL)  sum += ls->sum;
		if(rs != NULL)  sum += rs->sum;
}
inline void pushdown(){
		if(ls != NULL) {
			ls->maketag(lazy);
		}	
		if(rs != NULL) 	rs->maketag(lazy);
		lazy = 0;
}
inline void upd(const int x1, const int x2, const int flg){
	    if(EqualRange(x1, x2)){
	        maketag(flg);
	    }
	    else if(!OutofRange(x1, x2)){
		pushdown();
	        if(ls != NULL) if(x1 < ls->r)ls->upd(x1, min(x2, ls->r), flg);
	        if(rs != NULL) if(x2 > rs->l)rs->upd(max(x1, rs->l), x2, flg);
	        pushup();
	    }
}

现在来强调最最最最重要的一个bug点,我在这里卡了一个小时。是OutofRange(L, R)函数。这个是基本函数了,由于是区间,端点的重合并不真的是在区间里,所以要加上等号!!!
对:

inline bool OutofRange(const int L, const int R){
  return (L >= r) || (l >= R);
}

错:

inline bool OutofRange(const int L, const int R){
  return (L > r) || (l > R);
}

把上面的int全改为longlong后仍旧WA的假code(TAT

#include<map>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<vector>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long 
#define mod 998244353
using namespace std;
int rd(){
	int res = 0, fl = 1;
   char c = getchar();
   while(!isdigit(c)){
       if(c == \'-\') fl = -1;
       c = getchar();
   }
   while(isdigit(c)){
      res = (res << 3) + (res << 1) + c - \'0\';
      c = getchar();
   }
   return res * fl;
}
namespace force{
int main(){
	return 0;
}
}
const int maxn = 100010;
struct line{
	ll y, x1, x2, flg;
}p[maxn * 2];
int n;
int num[maxn * 2];
ll ans;
struct Node{
	ll l, r, sum, lazy;
	Node *ls, *rs;
	inline bool EqualRange(const ll L, const ll R){
		return (L == l) && (r == R);
	}
	inline bool OutofRange(const ll L, const ll R){
		return (L >= r) || (l >= R);
	}
	inline void maketag(const ll flg){
	    lazy += flg;
	    if(lazy <= 0)  sum = 0;
	    if(lazy > 0)  sum = r - l;
	}
	inline void pushup(){
		sum = 0;
		if(ls != NULL)  sum += ls->sum;
		if(rs != NULL)  sum += rs->sum;
	}
	inline void pushdown(){
		if(ls != NULL) {
			ls->maketag(lazy);
		}	
		if(rs != NULL) 	rs->maketag(lazy);
		lazy = 0;
	}
	inline void upd(const ll x1, const ll x2, const ll flg){
	    if(EqualRange(x1, x2)){
	        maketag(flg);
	    }
	    else if(!OutofRange(x1, x2)){
			pushdown();
	        if(ls != NULL) if(x1 < ls->r)ls->upd(x1, min(x2, ls->r), flg);
	        if(rs != NULL) if(x2 > rs->l)rs->upd(max(x1, rs->l), x2, flg);
	        pushup();
	    }
//	    printf("[%d,%d], lazy=%d, sum=%d\\n", l, r, lazy, sum);
	}
}Mem[maxn * 4], *pool = Mem;
Node* New(){
	return ++pool;
}
Node* build(const int L, const int R){
	Node *u = New();
	u->lazy = 0;
	u->l = num[L];
	u->r = num[R];
	if(R - L <= 1){
		u->sum = 0;
	}
	else{
		int M = L + R >> 1;
		u->ls = build(L, M);
		u->rs = build(M, R);
		u->pushup();
	}
	return u;
}
bool cmp(line a, line b){
	if(a.y == b.y)	return a.flg < b.flg;
	return a.y < b.y;
}
int main(){
	n = rd();
	int x1, x2, y1, y2;
	for(int i = 1; i <= n; ++i){
		x1 = rd(); y1 = rd();  x2 = rd(); y2 = rd();
		p[i].y = y1;
		p[i].x1 = x1;
		p[i].x2 = x2;
		p[i].flg = 1;
		p[i + n].y = y2;
		p[i + n].x1 = x1;
		p[i + n].x2 = x2;
		p[i + n].flg = -1;
		num[i] = x1;
		num[i + n] = x2;
	}
	sort(num + 1, num + 1 + 2 * n);
	sort(p + 1, p + 1 + 2 * n, cmp);
	Node *rot = build(1, 2 * n);
	rot->upd(p[1].x1, p[1].x2, p[1].flg);
//	printf("sum %d\\n", rot->sum);
	for(int i = 2; i <= n * 2; ++i){
//		printf("line: %d %d\\n", p[i].x1, p[i].x2);
		ans += (p[i].y - p[i - 1].y) * (rot->sum);
		rot->upd(p[i].x1, p[i].x2, p[i].flg);
//		printf("sum %d\\n", rot->sum);
	}
	printf("%lld\\n", ans);
	return 0;
}
/*
3
100 100 150 150
150 150 200 200
200 200 250 255
*/


以上是关于扫描线——~~原理分析与代码块(指针线段树)~~——从入门到放弃的主要内容,如果未能解决你的问题,请参考以下文章

扫描线算法的介绍与论证

2021牛客暑期多校训练营4 E.Tree Xor 线段树+扫描线原理

线段树+扫描线求矩形面积的并

练习:线段树+扫描线

扫描线

USACO Overplanting ( 线段树扫描线 )