题目大意:给你n个矩形,求它们的周长并,n ≤ \le ≤ 5e3,坐标范围[-1e4, 1e4]。
难度:Ag+
分析:
每个矩形可以拆成四条线段,两条水平,两条竖直。对于一个矩形拆成的两条水平线段,其左右端点的横坐标相同,纵坐标不同,把纵坐标小的叫第一类水平线段,纵坐标大的叫第二类水平线段。首先维护一个数轴,其上所有点均未被线段覆盖。然后对于所有水平的线段,按线段的纵坐标从小到大排序。遍历排好序的水平线段,对于第一类水平线段,将其插入到数轴上,对于第二类水平线段,将其对应的第一类水平线段从数轴上删除。每次插入和删除之后,统计数轴中有多少点被至少一条线段覆盖,其值和上一次统计的结果做差,差的绝对值就是对答案的贡献。(至于原因,可以画图来看一下)
对于竖直的线段,可以类比水平线段的做法。
现在问题转换成,一个数轴,三种操作:
- 在数轴上插入一条线段
- 在数轴上删除一条线段
- 查询数轴上有多少个点被至少一条线段覆盖
发现可以使用线段树来维护这个数轴,线段树每个节点维护两个值a和b,a表示区间里被至少一条线段覆盖的点数,b表示区间被直接覆盖的次数。
对于操作1,线段对应的区间,可以在线段树上被拆分成一些子区间,这些子区间的对应节点的b值加1,由于这些子区间里每一个点现在都被至少一条线段覆盖了,对应节点的a值变为区间长度。
对于操作2,同操作1一样把线段对应的区间拆分成子区间,这些子区间的对应节点的b值减1,如果某个叶节点的b值被减为0,显然应该把这个叶节点的a值也赋为0,如果某个非叶节点的b值被减为0,则该节点的a值等于其左右子节点的a值之和(因为虽然没有线段直接覆盖该区间,但有可能直接覆盖该区间的子区间)。
对于操作3,直接返回根节点的a值。
复杂度:O(nlogm),n表示矩形数,m表示坐标范围
代码:
# include <bits/stdc++.h>
# define MAXN 20005
using namespace std;
struct Line
{
int pos, x, y, val; //pos表示线段的横(纵)坐标,x和y分别表示两个端点的纵(横)坐标,val表示应该插入还是删除
bool operator < (const Line & l) const
{
if (pos == l.pos)
return val > l.val; //先插入,再删除
return pos < l.pos;
}
};
struct SGT
{
int n;
int a[2][MAXN << 2]; //a[0]表示区间里被覆盖的点数,a[1]表示区间直接被覆盖的次数
void build(int size)
{
n = size;
for (int i = 0; i < 2; ++i)
memset(a[i