hdu1828 Picture(线段树+扫描线+矩形周长并)

转自:http://blog.csdn.net/tomorrowtodie/article/details/52048323

说完了矩形面积,矩形周长的方法自然是类似的,但是周长的计算却更复杂些,看这张图:













周长可以分成两部分计算,横线和竖线,如图将所有彩色的横线加起来就是横向的所有长度了

然后可以采用竖直方向的扫描线将竖线的所有长度求出来

那么怎么计算横线的长度呢?

横线的长度 = 【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】

想想为什么要加绝对值(提示:下边--删除,上边--插入)

这样用自下而上从左往右两次扫描即可得到答案。

但是,这样的方法显得有些笨,有没有更高端的方法呢?

再看一张图:













看出什么了吗?这张图在上面那张图的基础上多了几条竖线。

我的意思是说,我们可以只做一次自下而上的扫描就把横线竖线都算出来!

竖线的算法和上面说的方法一样:【现在这次总区间被覆盖的程度和上一次总区间被覆盖的长度之差的绝对值】

竖线要怎么计算?

首先我们现在改一下线段树保存的属性,我们用如下信息记录线段树的节点:

1. l , r : 该节点代表的线段的左右端点坐标

2.len : 这个区间被覆盖的长度(即计算时的有效长度)

3.s : 表示这个区间被覆盖了几次

4. lc , rc : 标记这个节点的左右两个端点是否被覆盖(0表示没有,1表示有)

5.num :这个区间有多少条线段(这个区间被多少条线段覆盖)

这里的num涉及到竖线的计算,故解释一下,举几个例子:

   若区间[0,10]被[1,2][4,5]覆盖,则num = 2

   若区间[0,10]被[1,3][4,5]覆盖,则num = 1(两区间刚好连在一起)

   若区间[0,10]被[1,5][2,6]覆盖,则num = 1(两区间连起来还是一段)

然后就可以计算竖线了:

竖线的长度 = 【下一条即将被扫到的横线的高度 - 现在扫到的横线的高度】*2*num

乘2是因为每条线段有两个端点;

看上图中棕色线段的竖线有4条,因为棕色的横线由2条线段组成

白色线段的竖线只有2条,因为白色的横线由1条线段组成(虽然这1条线段是由许多线段组合而成,但它依旧只算1条线段)

这样,依旧只扫一次就可以算出周长。


#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;
}


  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值