线段树经典题目 矩形面积

有N(100000)个长方形,并且它的边长平行于x轴或y轴(范围50000)。求这N个长方形覆盖的面积,重叠部分的面积只算一次。

在平面上的一些统计的题目,往往是用线段树,把x轴作为扫描线,然后把y轴映射到线段树上。这题也是如此:

若坐标范围太大,可以将其离散化,这题不需要。如图,有两个矩形。


竖细线作为扫描线,把y轴映射到线段树。在处理每一条扫描线的同时,首先统计线段树中已经被覆盖的总长度。这一次扫描的面积要加上:两条扫描线的之间的差 *  被覆盖的总长度。

下面是线段树上的值的变化:

x = 1:0 1 1 1 0 0   被覆盖总长度:3   

x = 2:0 1 2 2 1 1   被覆盖总长度:4    面积加上:(2 - 1) * 3

x = 3:0 0 1 1 1 1   被覆盖总长度:4    面积加上:(3 - 2) * 4

x = 5:0 0 0 0 0 0   被覆盖总长度:0    面积加上:(5 - 3) * 4

现在需要维护被覆盖的总长度,我知道有两种方法:

一是统计线段树中,等于0的有多少个,记下最小值以维护,然后用y轴的总长度50000减去即可。二是对于线段树中每个结点所表示的区间被整段覆盖了多少次,记为cover,如果有一个操作是将这个区间整段覆盖(取消覆盖)一次,那么cover加一(减一),如果不是整段覆盖,那么就递归左子树或右子树。

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;

inline int getInt() 
{
	int res = 0;
	char ch;
	do 
		ch = getchar(); 
	while (ch < '0' || ch > '9');
	do 
	{
		res = res * 10 + (int) ch - (int) '0'; 
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9');
	return res;
}

int n;
const int N = 100007;
const int L = 50007;

struct line
{
	int x, y1, y2;
	bool ins;
	bool operator < (line const &o) const 
	{
		return x < o.x;
	}
}event[N << 1];

void Init()
{
	n = getInt();
	for (int i = 0; i < n; i ++)
	{
		event[i << 1].x = getInt();
		event[i << 1].y1 = getInt();
		event[(i << 1) ^ 1].x = getInt();
		event[i << 1].y2 = getInt();
		event[(i << 1) ^ 1].y1 = event[i << 1].y1;
		event[(i << 1) ^ 1].y2 = event[i << 1].y2;
		event[i << 1].ins = true;
		event[(i << 1) ^ 1].ins = false;
	}
	sort(event, event + (n << 1));
}

struct Node 
{
	Node *lch, *rch;
	int lo, hi;
	int cnt, cov;
	inline int size()
	{
		return hi - lo;
	}
	inline int mi()
	{
		return (lo + hi) >> 1;
	}
}node[L << 1], *tree = &node[0];
int nnode = 1;

void Build(Node *&p, int lo, int hi)
{
	p -> lo = lo;
	p -> hi = hi;
	if (lo + 1 == hi)
		p -> lch = p -> rch = NULL;
	else 
	{
		int mi = p -> mi();
		p -> lch = &node[nnode ++];
		p -> rch = &node[nnode ++];
		Build(p -> lch, lo, mi);
		Build(p -> rch, mi, hi);
	}
}

void Modify(Node *p, int le, int ri, bool ins)
{
	if (le <= p -> lo && ri >= p -> hi)
		if (ins)
		{
			p -> cov ++;
			p -> cnt = p -> size();
		}
		else 
		{
			p -> cov --;
			if (p -> cov) p -> cnt = p -> size();
			else 
			{
				p -> cnt = 0;
				if (p -> lch) p -> cnt += p -> lch -> cnt;
				if (p -> rch) p -> cnt += p -> rch -> cnt;
			}
		}
	else
	{
		int mi = p -> mi();
		if (le < mi) Modify(p -> lch, le, ri, ins);
		if (ri > mi) Modify(p -> rch, le, ri, ins);
		if (p -> cov) p -> cnt = p -> size();
		else p -> cnt = p -> lch -> cnt + p -> rch -> cnt;
	}
}

void Solve()
{
	ll ans = 0LL;
	
	for (int i = 0; i < n << 1; i ++)
	{
		if (i) ans += (event[i].x - event[i - 1].x) * tree -> cnt;
		Modify(tree, event[i].y1, event[i].y2, event[i].ins);
		//printf("%d %d %I64d\n", i, tree -> cnt, ans);
	}
	
	printf("%I64d\n", ans);
}

int main() 
{
	freopen("area.in", "r", stdin);
	freopen("area.out", "w", stdout);
	
	Init();
	Build(tree, 0, L + 1);
	Solve();
	
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值