扫描线+线段树
基本思路
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;
}