线段树扫描线求矩形周长详解

线段树扫描线求矩形周长详解

线段树扫描线详解

OI比赛中扫描线是一种很常用的算法,矩形周长是一个模板题。

题目链接

基本思想

比如,对于下面的三个矩形:

        想象有一条扫描线,从下往上扫描完整个图案,每遇到一条上边或者下边就停下来:

    然后每次停下后对区间进行处理,用一个ans代表当前周长,最后ans就是答案。

代码实现

    首先,要先把矩形拆成上边和下边,用1和-1分别代表上边和下边。然后按高度排序,这样数组从前往后处理就相当于扫描线从下往上扫描。如果是下边,就在对应区间上加1,如果是上边,就在对应区间上减1。

    在整个区间上建一棵线段树:


 
 
  1. #define lson o<<1
  2. #define rson o<<1|1
  3. #define mid (l+r)/2
  4. struct Tree
  5. {
  6.     int sum; //整个区间被整体覆盖了几次(类似lazytag,但不下传)
  7.     int num; //整个区间被几条互不相交的线段盖(比如,[1,2],[4,5]则为2,[1,3],[4,5]则为1(我习惯用闭区间),[1,4],[2,2],[4,4]也为1)
  8.     int len; //整个区间被覆盖的总长度
  9.     bool lflag; //左端点是否被覆盖(合并用)
  10.     bool rflag; //右端点是否被覆盖(合并用)
  11. } //如果不懂也没有关系,接着往下看

那么pushup要怎么写呢?


 
 
  1. void pushup(int o,int l,int r)
  2. {
  3.     if(tree[o].sum) //此区间之前被一整个线段覆盖过
  4.     {
  5.         tree[o].num= 1;
  6.         tree[o].len=r-l+ 1;
  7.         tree[o].lflag=tree[o].rflag= 1;
  8.     }
  9.     else if(l==r) //这是一个叶节点
  10.     {
  11.         tree[o].num= 0;
  12.         tree[o].len= 0;
  13.         tree[o].lflag=tree[o].rflag= 0;
  14.     }
  15.     else //一般情况
  16.     {
  17.         tree[o].num=tree[lson].num+tree[rson].num;
  18.         if(tree[lson].rflag==tree[rson].lflag)tree[o].num--; //flag的用处
  19.         tree[o].len=tree[lson].len+tree[rson].len;
  20.         tree[o].lflag=tree[lson].lflag;
  21.         tree[o].rflag=tree[rson].rflag;
  22.         //注意:sum不会被修改,只有当它被一整个线段覆盖时才会修改
  23.     }
  24. }

有了pushup,add函数就好写了:


 
 
  1. void add(int o,int l,int r,int from,int to,int value)
  2. //此区间为[l,r],待修改区间为[from,to],添加值为value。
  3. {
  4.     if(l>=from&&r<=to) //被整个覆盖
  5.     {
  6.         tree[o].sum+=value;
  7.         pushup(o,l,r);
  8.         return;
  9.     }
  10.     if(from<=mid)add(lson,l,mid,from,to,value);
  11.     if(to>mid)add(rson,mid+ 1,r,from,to,value);
  12.     pushup(o,l,r);
  13. }

流程

Step 0:build

Step 1:add(1,1,6,1,5,1)

先递归处理:

再pushup:

Step 2:add(1,1,6,2,3,1)

Step 3:add(1,1,6,4,5,1)

(懒得分开写了)

递归处理,pushup:

Step 4:add(1,1,6,1,5,-1)

Step 5:add(1,1,6,2,3,-1)

Step 6:add(1,1,6,5,6,-1)

至此,总算说完了线段树的修改。

    突然想起一个很严肃的事情来:答案怎么统计?

    对于横边,相邻两次修改的区间覆盖长度差(就是tree[root].len的差)加起来就是答案(不理解的自己想办法理解,反正我不理解);

    对于竖边,你可以再让扫描线从左到右扫一遍~~不过还是说简单法吧。我们需要记录整个区间有多少个端点(包含在线段内不算),然后用它乘上相邻两次修改的高度差。

    怎么记录呢?

    对了,就是tree[root].num*2*(h2-h1)(num的作用在这里)

    好了,下面是完整代码:


 
 
  1. #include<cstdio>
  2. #include<algorithm>
  3. #include<cstring>
  4. #define lson o<<1
  5. #define rson o<<1|1
  6. #define mid (l+r)/2
  7. using namespace std;
  8. struct Edge
  9. {
  10. int left;
  11. int right;
  12. int height;
  13. int flag;
  14. }e[ 10005];
  15. struct Tree
  16. {
  17. int sum;
  18. int num;
  19. int len;
  20. bool lflag;
  21. bool rflag;
  22. }tree[ 100005];
  23. int n,mx= -2147483647,mn= 2147483647,edgenum,ans,last;
  24. void add_edge(int l,int r,int h,int f)
  25. {
  26. e[++edgenum].left=l;
  27. e[edgenum].right=r;
  28. e[edgenum].height=h;
  29. e[edgenum].flag=f;
  30. }
  31. bool cmp(Edge a,Edge b)
  32. {
  33. return a.height<b.height||a.height==b.height&&a.flag>b.flag;
  34. }
  35. void pushup(int o,int l,int r)
  36. {
  37. if(tree[o].sum)
  38. {
  39. tree[o].num= 1;
  40. tree[o].len=r-l+ 1;
  41. tree[o].lflag=tree[o].rflag= 1;
  42. }
  43. else if(l==r)
  44. {
  45. tree[o].len= 0;
  46. tree[o].num= 0;
  47. tree[o].lflag=tree[o].rflag= 0;
  48. }
  49. else
  50. {
  51. tree[o].len=tree[lson].len+tree[rson].len;
  52. tree[o].num=tree[lson].num+tree[rson].num;
  53. if(tree[lson].rflag&&tree[rson].lflag)tree[o].num--;
  54. tree[o].lflag=tree[lson].lflag;
  55. tree[o].rflag=tree[rson].rflag;
  56. }
  57. }
  58. void add(int o,int l,int r,int from,int to,int value)
  59. {
  60. if(l>=from&&r<=to)
  61. {
  62. tree[o].sum+=value;
  63. pushup(o,l,r);
  64. return;
  65. }
  66. if(from<=mid)add(lson,l,mid,from,to,value);
  67. if(to>mid)add(rson,mid+ 1,r,from,to,value);
  68. pushup(o,l,r);
  69. }
  70. int main()
  71. {
  72. scanf( "%d",&n);
  73. for( int i= 1;i<=n;i++)
  74. {
  75. int x1,y1,x2,y2;
  76. scanf( "%d%d%d%d",&x1,&y1,&x2,&y2);
  77. mx=max(mx,max(x1,x2));
  78. mn=min(mn,min(x1,x2));
  79. add_edge(x1,x2,y1, 1);
  80. add_edge(x1,x2,y2, -1);
  81. }
  82. if(mn<= 0)
  83. {
  84. for( int i= 1;i<=edgenum;i++)
  85. {
  86. e[i].left+=-mn+ 1;
  87. e[i].right+=-mn+ 1;
  88. }
  89. mx-=mn;
  90. }
  91. sort(e+ 1,e+edgenum+ 1,cmp);
  92. for( int i= 1;i<=edgenum;i++)
  93. {
  94. add( 1, 1,mx,e[i].left,e[i].right -1,e[i].flag); //注意这里!!!加边有学问!!!
  95. while(e[i].height==e[i+ 1].height&&e[i].flag==e[i+ 1].flag)
  96. {
  97. i++;
  98. add( 1, 1,mx,e[i].left,e[i].right -1,e[i].flag);
  99. }
  100. ans+= abs(tree[ 1].len-last);
  101. last=tree[ 1].len;
  102. ans+=tree[ 1].num* 2*(e[i+ 1].height-e[i].height);
  103. }
  104. printf( "%d\n",ans);
  105. return 0;
  106. }

最后给两个卡死我的样例:

样例输入1:


 
 
  1. 7
  2. -15 0 5 10
  3. -5 8 20 25
  4. 15 -4 24 14
  5. 0 -6 16 4
  6. 2 15 10 22
  7. 30 10 36 20
  8. 34 0 40 16

样例输出1:

228
 
 

样例输入2:


 
 
  1. 2
  2. 0 0 4 4
  3. 0 4 4 8

样例输出2:

24
 
 
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
 
const int maxn = 22222;
struct Seg {
	int l, r, h, s;
	Seg() {}
	Seg(int a, int b, int c, int d) :l(a), r(b), h(c), s(d) {}
	bool operator < (const Seg &cmp) const {
		return h < cmp.h;
	}
}ss[maxn];
bool lbd[maxn << 2], rbd[maxn << 2];//标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)
int numseg[maxn << 2];//这个区间有多少条线段(这个区间被多少条线段覆盖)
int cnt[maxn << 2];//表示这个区间被重复覆盖了几次  
int len[maxn << 2];//这个区间被覆盖的长度 
void PushUP(int rt, int l, int r) {
	if (cnt[rt]) {//整个区间被覆盖  
		lbd[rt] = rbd[rt] = 1;
		len[rt] = r - l + 1;
		numseg[rt] = 2;//每条线段有两个端点
	}
	else if (l == r) {//这是一个点而不是一条线段 
		len[rt] = numseg[rt] = lbd[rt] = rbd[rt] = 0;
	}
	else {//是一条没有整个区间被覆盖的线段,合并左右子的信息
		lbd[rt] = lbd[rt << 1]; // 和左儿子共左端点
		rbd[rt] = rbd[rt << 1 | 1]; //和右儿子共右端点
		len[rt] = len[rt << 1] + len[rt << 1 | 1];
		numseg[rt] = numseg[rt << 1] + numseg[rt << 1 | 1];
		if (lbd[rt << 1 | 1] && rbd[rt << 1]) numseg[rt] -= 2;//如果左子的右端点和右子的左端点都被覆盖了即两条线重合
	}
}
void update(int L, int R, int c, int l, int r, int rt) {
	if (L <= l && r <= R) {
		cnt[rt] += c;
		PushUP(rt, l, r);
		return;
	}
	int m = (l + r) >> 1;
	if (L <= m) update(L, R, c, lson);
	if (m < R) update(L, R, c, rson);
	PushUP(rt, l, r);
}
int main() {
	int n;
	while (~scanf("%d", &n)) {
		int m = 0;
		int lbd = 10000, rbd = -10000;
		for (int i = 0; i < n; i++) {
			int a, b, c, d;
			scanf("%d%d%d%d", &a, &b, &c, &d);
			lbd = min(lbd, a);
			rbd = max(rbd, c);
			ss[m++] = Seg(a, c, b, 1);
			ss[m++] = Seg(a, c, d, -1);
		}
		sort(ss, ss + m);
		//数据小可以不离散化 
		int ret = 0, last = 0;
		for (int i = 0; i < m; i++) {
			if (ss[i].l < ss[i].r) update(ss[i].l, ss[i].r - 1, ss[i].s, lbd, rbd - 1, 1);
			ret += numseg[1] * (ss[i + 1].h - ss[i].h);//竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*num
			ret += abs(len[1] - last);//横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】
			last = len[1];//每次都要更新上一次总区间覆盖的长度  
		}
		printf("%d\n", ret);
	}
	return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值