[0.x]前言

扫描线算法(ScanLine)用于求矩阵面积并或周长并,内核为线段树维护离散化的线段并在线处理。

语言:[算法] 扫描线 - 图1

为了节省时间,图片全部引自这篇Luogu题解

目录

  • [1.x]扫描线概述
  • [2.x]扫描线实现
    • [2.1.x]预处理&离散化
    • [2.2.x]线段树维护
    • [2.3.x]在线处理
  • [3.x]相关代码

[1.x]扫描线概述

扫描线一般用于处理以下问题:

读入若干矩阵,求矩阵的周长并/面积并。

[算法] 扫描线 - 图2

该问题的处理难点在于:

  • 当用点来维护矩阵的时候,数值可能过大,需要离散化
  • 维护完点集以后,暴力计算处理繁复而且缓慢,需要线段树维护

我们用线段树维护从下到上的每一个线段,每一次向上推移一个离散后的单位长度,进行求解,就可以得到每一部分的面积

[算法] 扫描线 - 图3

  • 如我们维护x[1]~x[3]的长度,乘以两条红色线之间的距离,就是这个绿色区域的面积

[2.x]扫描线实现

[2.1.x]预处理&离散化

我们需要预处理的东西主要有以下几个:

  • 维护每一条横向线段,包括左端点右端点,以及线段的高度和属性(即这条边是一个矩形的上边还是下边
  1. line[++tot].l = x_1 , line[tot].r = x_2 , line[tot].h = y_1 , line[tot].v = 1;
  2. line[++tot].l = x_1 , line[tot].r = x_2 , line[tot].h = y_2 , line[tot].v = -1;
  3. //我们定义下边的权值为+1,因为我们扫到一个下边就意味着有一个新的区域要加入计算
  4. //我们定义上边的权值为-1,因为我们扫到一个上边就意味着有一个旧的区域要脱离计算
  5. std::sort(line+1,line+tot_line+1,cmp);
  6. //按照高度(line[tot].h)排序,因为我们处理的时候是从下往上处理的
  • 维护所有端点的横座标并去重以便离散化
  1. //两个代码框之间的tot不共通,只是为了缩小代码块码量,请主要区分
  2. point[++tot] = x_1 , point[++tot] = x_2;
  3. //存入所有的横座标
  4. std::sort(point+1,point+tot_point+1,cmp);
  5. tot_x = std::unique(point+1,point+tot+1)-point-1;
  6. //排序后去重,用来离散化

[2.2.x]线段树维护

我们用线段树维护线段而非点。

对于一个节点cur,我们有这么几个属性:

  • cur->l,表示离散化后的该线段左端点序号
  • cur->r,表示离散化后的该线段右端点序号的前一个,即真正的右端点应该为point[cur->r+1]
  • cur->len,表示当前节点所表示的线段里已经被标记过的有多长
  • cur->sum,表示该线段被标记与否
    • 其实这里可以用bool类型,但是我们刚才定义了上下边为1/-1,所以可以用int的加减来实现0/1T/F的转换
  • cur->ls``cur->rs,左儿子右儿子,不解释
  1. struct node{
  2. int l,r;
  3. ll sum,len;
  4. node * ls, * rs;
  5. }Tree[4*N];

其中特别要注意的就是,我们维护的是线段,而cur->r这个属性维护的前一个序号,这就能保证线段树的线段没有重复但是维护的线段端点可以重合。

另外,之所以这里不用打tag,是因为这里有个特殊的性质:

  • 对于一个节点,也就是对于一条特定的线段,它一定存在偶数个待处理线段,这是由输入的东西是矩阵保证的。换句话说,你要是这个节点被标记了,那说明这上面的一定都可以,不存在因为子节点的修改而导致该节点的变化。所以既不需要pushdown也不需要lazy_tag
  • 再换句话说,对于线段上的任意一点,只要经过改点的线段有一条是被标记的,就可以纳入计算。
  1. inline node * create(){return &Tree[++tot_tree];} // 线段树申请节点
  2. inline void build(node * cur,int l,int r){ // 线段树建树
  3. cur->l = l , cur->r = r , cur->sum = cur->len = 0; // 节点初始化
  4. if(l == r) return;
  5. int mid = (l+r)>>1;
  6. cur->ls = create() , cur->rs = create();
  7. build(cur->ls,l,mid) , build(cur->rs,mid+1,r);
  8. return;
  9. }
  10. inline void pushup(node * cur){ // 喜闻乐见的pushup
  11. if(cur->sum) cur->len = point[cur->r+1] - point[cur->l]; // 如果当前节点被标记就直接用当前线段长度
  12. else cur->len = cur->ls->len + cur->rs->len; // 当前节点没被标记就用儿子的
  13. }
  14. inline void edit(node * cur,int k){
  15. int l = cur->l , r = cur->r;
  16. if(L >= point[r+1] || R <= point[l]) return;
  17. if(L <= point[l] && R >= point[r+1]){
  18. cur->sum += k;
  19. if(cur->l != cur->r) pushup(cur);
  20. else{ if(cur->sum) cur->len = point[cur->r+1] - point[cur->l];else cur->len = 0;}
  21. return;
  22. }
  23. edit(cur->ls,k) , edit(cur->rs,k);
  24. pushup(cur);
  25. return;
  26. }
  27. int main(){
  28. ...
  29. node * root = create();
  30. build(root,1,tot_x-1); // n个点,n-1个独立线段
  31. ...
  32. }

[2.3.x]在线处理

我们现在已经用线段树维护这些线段了,也维护了从下到上的线段,我们只需要从下到上一个个枚举这些线段就行了。

  1. rep(i,1,tot_line){
  2. L = line[i].l , R = line[i].r; // 全局变量减少函数传参
  3. edit(root,line[i].v);pushup(root); // 在线维护扫描线上的标记长度
  4. ans += root->len * (line[i+1].h - line[i].h); // 计算
  5. }

[3.x]相关代码

P5490 【模板】扫描线

  1. #include<map>
  2. #include<set>
  3. #include<cmath>
  4. #include<queue>
  5. #include<bitset>
  6. #include<vector>
  7. #include<cstdio>
  8. #include<cstring>
  9. #include<cstdlib>
  10. #include<iostream>
  11. #include<algorithm>
  12. #define rep(i,a,b) for(register int i = (a);i <= (b);++i)
  13. #define per(i,a,b) for(register int i = (a);i >= (b);--i)
  14. typedef long long ll;
  15. typedef unsigned long long ull;
  16. using std::string;using std::cin;using std::cout;
  17. const int N = 100010;
  18. int n,x_1,x_2,y_1,y_2,tot_line,tot_x,tot_point,tot_tree,L,R;
  19. ll point[4*N],ans;
  20. struct LINE{
  21. int l,r,h,v;
  22. }line[4*N];
  23. struct node{
  24. int l,r;
  25. ll sum,len;
  26. node * ls, * rs;
  27. }Tree[4*N];
  28. inline bool cmp1(LINE a,LINE b){return a.h<b.h;}
  29. inline bool cmp2(int x,int y){return x<y;}
  30. inline node * create(){return &Tree[++tot_tree];}
  31. inline void build(node * cur,int l,int r){
  32. cur->l = l , cur->r = r , cur->sum = cur->len = 0;
  33. if(l == r) return;
  34. int mid = (l+r)>>1;
  35. cur->ls = create() , cur->rs = create();
  36. build(cur->ls,l,mid) , build(cur->rs,mid+1,r);
  37. return;
  38. }
  39. inline void pushup(node * cur){
  40. if(cur->sum) cur->len = point[cur->r+1] - point[cur->l];
  41. else cur->len = cur->ls->len + cur->rs->len;
  42. }
  43. inline void edit(node * cur,int k){
  44. int l = cur->l , r = cur->r;
  45. if(L >= point[r+1] || R <= point[l]) return;
  46. if(L <= point[l] && R >= point[r+1]){
  47. cur->sum += k;
  48. if(cur->l != cur->r) pushup(cur);
  49. else{ if(cur->sum) cur->len = point[cur->r+1] - point[cur->l];else cur->len = 0;}
  50. return;
  51. }
  52. edit(cur->ls,k) , edit(cur->rs,k);
  53. pushup(cur);
  54. return;
  55. }
  56. int main(){
  57. std::ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  58. //freopen("in.in", "r", stdin);
  59. cin >> n;
  60. rep(i,1,n){
  61. cin >> x_1 >> y_1 >> x_2 >> y_2;
  62. line[++tot_line].l = x_1 , line[tot_line].r = x_2 , line[tot_line].h = y_1 , line[tot_line].v = 1;
  63. line[++tot_line].l = x_1 , line[tot_line].r = x_2 , line[tot_line].h = y_2 , line[tot_line].v = -1;
  64. point[++tot_point] = x_1 , point[++tot_point] = x_2;
  65. }
  66. std::sort(line+1,line+tot_line+1,cmp1) , std::sort(point+1,point+tot_point+1,cmp2);
  67. tot_x = std::unique(point+1,point+tot_point+1)-point-1;
  68. node * root = create();
  69. build(root,1,tot_x-1);
  70. rep(i,1,tot_line){
  71. L = line[i].l , R = line[i].r;
  72. edit(root,line[i].v);pushup(root);
  73. ans += root->len * (line[i+1].h - line[i].h);
  74. }
  75. cout << ans << "\n";
  76. return 0;
  77. }