矩阵快速幂的简单理解
面积并
题解
参考洛谷第一篇题解
如上图,我们要获得所有矩形的面积并,用一条扫描线从图中最下面的红线往上面扫,扫到的面积就记录下来,记录的是这一段被覆盖的面积,也就是当前高度(h1=4)到它之上的红线高度(h2=7)之差(3)和这一整段有效长度的乘积(s1=14-2=12)。
之后红线继续上移到h2=7,记录它到它之上的红线h3=9的差Δh=2,有效长度为18,s2=18*2=36。
直到扫完s5。
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
思路非常清晰,那么现在就剩下两个问题
1.蓝色的扫描线怎么移动?
这个问题比较简单。如果有n个矩形,我们就用一个结构体line存进2*n条边(上边和下边),它应该记录所有这条边的信息(左端点l,右端点r,高度h,还有一个很重要的性质:这条边是矩形的上边还是下边,我们用1表示下边,用-1表示上边,等一下说明这么设置的作用)存完按升序排序,那我们按顺序在line里面查找就可以让扫描线从最下面的红线一直往上面扫直到最上面一条了。
struct Line{
double l,r,h;
int d;
bool operator < (Line const &x)const {
return h<x.h;
}
}line[N<<1];//建立扫描线
2.每一个高度的区间有效长度怎么记录?
①在线段树里增加一个num变量,用来保存现在节点表示的区间被多少个矩形覆盖到,那么在扫描线扫到一条下边的时候,这条边对应的节点num应该加1,因为又有多一个矩形覆盖到这个节点表示的区间了,扫描到一条上边的时候,这条边对应到的节点num应该减1,因为有一个矩形已经结束覆盖这个区间了。这时候我们存储在line中表示上边下边的信息d就起了作用,更新其实是一个简单区间修改的过程。
void push_up(int k){
if(tr[k].num)tr[k].len=x[tr[k].r+1]-x[tr[k].l];
else tr[k].len=tr[k<<1].len+tr[k<<1|1].len;
}
void update(int k,double l,double r,int d){
int L=tr[k].l,R=tr[k].r;
if(l>=x[R+1]||r<=x[L])return;
if(l<=x[L]&&x[R+1]<=r){
tr[k].num+=d;
push_up(k);
return;
}
update(k<<1,l,r,d);
update(k<<1|1,l,r,d);
push_up(k);
}
②区间问题我们很容易想到线段树。在想获取s1的时候,左端点是2,右端点是14,如果是离散的点,我们修改一下区间,把每一个单元长度都增加1,但是现在是连续的,而且区间范围是0-1E9,很明显我们开不了这么大的线段树,但注意到矩形个数只有100个,我们采用离散化的方式,把区间端点映射到比较小的整型数上,我们的例子中x=2,8,10,13,14,20,分别映射到1,2,3,4,5,6,就可以把这些数据范围比较小的整型数作为端点拿去建线段树了。
②建树的过程有一点特别,我们让每一个节点都表示一个以左端点存储坐标为起点的区间而不是一个坐标,因为表示坐标的话没有什么意义,当树的左节点和右节点相同,左节点和右节点坐标相同,区间长度都是0,还有什么好维护的=^= 。改成区间其实也很简单,我们让每次更新长度都用当前区间的最右边的端点存储的x坐标减掉最左边端点储存的x坐标就是当前区间的长度了。但是想想更新到叶子节点的情况,左右端点存储的x坐标一定相等,这时候我们区间长度就是右端点再往右一个端点存储的坐标减去左端点存储的坐标。
因此干脆让所有节点的区间长度都使用到叶子节点的那种情况储存的方法。我们在更新区间的时候,右区间设置为原本的往左移动一个端点。这很容易实现,只要在建树的时候不加入最后一个端点就可以了。
build(1,1,tol-1);