蓝桥杯----油漆面积(暴力,线段树扫描线)

扫描线+线段树

基本思路

4.png

 1.运用扫描线从左到右扫描各个x时的高度获得然后计算这个当前x到下一个x的区间高度len(由于可能会涉及到离散的方块和重叠的区,就运用线段树计算)

2.运用len*每个区间的宽(x2-x1)得到这个区间的面积,如果就可以分为5个区间

扫描线:是用来扫描一个区间的

线段树:是用来计数区间里的阴影高度的

线段树数据结构

struct node
{
    int l;
    int r;
    int k;                //当前被覆盖了几次
    int len;              //被至少覆盖一次的区间总长度
                          //也可以看作区间[l,r+1]的阴影部分的高度
}

可能存在的问题+解答

1.线段树存储的是什么?

线段树l,r分别表示第l个区间,第r个区间,

所以(l,r)表示第l个区间到第r个区间,遍历长度是r-l+1

2.如果防止线段树重复计数

  我们通过扫描线扫描,把一个矩形的左竖线的权值k设为1,右竖线的权值k设为-1

这样子当扫描一个矩形时,一直碰到左竖线,我们就让他的k一直+1+1,碰到右竖线,我们就让他的k -1。如果k>0说明此时这个[l,r]区间仍然存在(应该被计数),如果k=0,就说明这个线段树不存在,那么它的值就应该修改

 我们要注意的是,只有区间被完全包含的时候,k值才可以被修改,所以要在计算当前区间高度之前把竖边先插入,不然获得不了区间的高度,使得答案不够准确

 

 我们应该在i=...时计算图中的面积,在当前计算完后,插入竖边到线段树中,然后去计算下一步计算面积时用到的阴影部分的高,那么我们i=2的时候,(5,9)的k变为0,此时就可以计算i=3时所需要的阴影部分的高了


基本流程

push_up:更新值,清空值

modify:加边,更新区间的阴影面积

bulid:建树 

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10050;
/*
	基本思路:
	1.线段树扫描所有区间
	2.运用线段树,add所有边,push_up一个区间内相应的高度
	3.res+=高度*一个区间长
*/
struct Segnament         //扫描线
{
	int x;
	int y1, y2;	//y2-y1=相应的区间高度
	int k;
	bool operator<(const Segnament& t)
	{
		return x < t.x;
	}
}seg[2*N];
//每个矩形有两条边,应有2*N个扫描线
struct node
{
	int l, r;
	//l,r是表示y值的区间[l,r]
	int cnt;			//当前被覆盖了几次(懒标记k)
	int len;			//区间中被覆盖至少一次的阴影部分面积的高度
}tr[4*N];
void pushup(int u)
{//因为有懒标记k的存在,这里的pushup比较特殊,需要考虑k的值进行pushup
//push_up:cnt>0,表示要用到这个区间,计算出相应的高度
//cnt=0,表示用不到,就要清空他的len,或者是类似堆往上更新值的操作
	if (tr[u].cnt > 0)				//当前区间被覆盖过了至少一次
	{//注意+1,因为l,r表示的是两个区间,而不是两个点
		tr[u].len = tr[u].r - tr[u].l + 1;
	}
	else
	{
		if (tr[u].l == tr[u].r)		//当cnt=0的时候,l==r,没有被覆盖
			tr[u].len = 0;			
		//没有被覆盖,那么说明当前阴影面积为0,
		//如果原本这里有阴影面积,cnt变为0,就是清空操作
		else					
			tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;		
		//不是颗粒区间,然后又没有被覆盖,应该是维护区间,
			//更新自己的len,上传自己的len给tr[1](类似堆的思想)
	}
}
void bulid(int u, int l, int r)
{
	if (l == r)
		tr[u] = { l,r,0,0 };
	else
	{
		int mid = l + r >> 1;
		tr[u] = { l,r,0,0 };	//记得写
		bulid(u << 1, l, mid);			//左边建树
		bulid(u << 1 | 1, mid+1, r);	//右边建树
	}
}
void modify(int u, int l, int r, int k)		
//扫描一个竖边得到时候,把竖边添加,权值k是不会改变的 等价于 const int k
//const int l,r,我要去寻找区间[l,r]的值,那么lr理应是不变的,变化了会导致查到的值不准确
{
	if (tr[u].l >= l && tr[u].r <= r)			
	{
		tr[u].cnt += k;
		pushup(u);					
	}//被完全包含:应该是让他的cnt+=k,表示被覆盖了k次
	//并且更新,if k=-1,使cnt变为0,--》清空,反之push_up是更新操作
	else
	{
		int mid = tr[u].l + tr[u].r >> 1;
		if(l<=mid)					//有包含的就要去修改,而不是完全包含才修改(有交集就去修改)
			modify(u << 1, l, r, k);
		if(r>mid)			//建树的时候r=mid+1
			modify(u << 1 | 1, l,r, k);
		//注意这里r>mid,不能r>=mid,因为r>=mid找不到答案陷入死循环
		pushup(u);			//更新
	}
}
int main()
{
	int n;				//矩形数
	int m = 0;
	cin >> n;
	int x1, y1, x2, y2;
	for (int i = 0;i < n;i++)
	{
		cin >> x1 >> y1>>x2>>y2;
		seg[m++] = { x1,y1,y2,1 };			//左竖边
		seg[m++] = { x2,y1,y2,-1 };			//右竖边
	}
	bulid(1, 0, 10000);
	long long res = 0;
	sort(seg, seg + m);						//排序,方便x轴从左到右遍历
	for (int i = 0;i < m;i++)
	{
		if (i > 0)
			res += tr[1].len * (seg[i].x - seg[i - 1].x);			//长*高=面积
		//这里如果两个x相同,就不会计数,等到x不同才会计数
		modify(1,seg[i].y1,seg[i].y2-1,seg[i].k);					//插入竖边
		//1根结点,y1表示起始区间,y2-1是因为区间数比坐标轴的整数小1,k表示权值
	}
	cout << res;
}

 2.暴力做法(50分)

#include <iostream>
#include <cstring>
using namespace std;
const int N = 10005;
int col[N][N];
typedef long long ll;
int main()
{
	int n;
	int x1, x2, y1, y2;
	cin >> n;
	//纯暴力做法
	while (n--)
	{
		cin >> x1 >> y1 >> x2 >> y2;
		for (int i = x1;i < x2;i++)
			for (int j = y1;j < y2;j++)
				col[i][j]++;
	}
	ll ans = 0;
	for (int i = 0;i < N;i++)
		for (int j = 0;j < N;j++)
			if(col[i][j])
				ans++;
	cout << ans;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值