线段树(内含矩形并)

常规线段树(非zkw)

部分内容来自:https://blog.csdn.net/WhereIsHeroFrom/article/details/78969718

含义

线段树,是一颗以区段划分为节点的二叉搜索树,查询效率logn,他优于ST表的地方在于,他可以解决动态RMQ问题

换一句话说,他可适用于当子结构的最优性可能发生改变的一类问题中

【例】给定一个n(n <= 100000)个元素的数组A,有m(m <= 100000)个操作,共两种操作:
1、Q a b         询问:表示询问区间[a, b]的元素和;
2、A a b c       更新:表示将区间[a, b]的每个元素加上一个值c;

表示方式

一.指针表示

每个结点可以看成是一个结构体指针,由数据域和指针域组成,其中指针域有两个,分别为左儿子指针和右儿子指针,分别指向左右子树;数据域存储对应数据(区间和,最大值)

二.数组表示

基于数组的静态表示法,需要一个全局的数组,每个结点对应数组中的一个元素,利用下标索引。

基本操作

1.构造

整体思路是二分递归,从区间[1, n]开始拆分,左半区间分配给左子树,右半区间分配给右子树,继续递归构造左右子树。注意回溯的时候传递左右子树的值,更新父节点的数据域。

2.查询

线段树的查询是指查询数组在[x, y]区间的值,同样也是自上而下地递归查询,不过一定要记得传参是五个值树节点位置,查询的左右端,现在的左右端(否则你再搞条件判断麻烦死了)

1.无交集 返回不传值

2.查询的左右段完全包含现在的左右端 返回并传值

3.不符合1,2,那么二分继续向下查找

3.更新

基本与查询无异

但是,为了提高更新的效率,所以每次更新只更新到更新区间完全覆盖线段树结点区间为止,这样就会使得被更新结点的子孙结点的区间得不到需要更新的信息,在下次查询的时候可能会因此忽略一些值。

所以我们有lazy-tag的标记优化(也叫延迟标记)

每一个结点都有一个lazy-tag标记,用来记录部分内容是没有对子节点往下处理过的。

而你一旦经过(查询或更新都算经过)这个含有lazy-tag的节点,就要做pushdown的操作,即释放标签值,左右子节点加上标签值(标签值下移)。

如果你现在是在更新,在像查询那样寻找更新区段完全包含的节点左右区段时,注意是更新完成了当前节点的data域,才给他贴上标签。

那么有些题目需要多个lazytag,那么我们就需要解决这样的标签冲突问题

1.加法标签和减法标签冲突:可直接混合运算

2.加法标签和覆盖标签冲突:遵循原则先覆盖后加

分析:如果产生标签冲突,那么在产生这种冲突情况的上一步状态必定是

(1)子节点具有加法标记,父节点具有覆盖标记,覆盖标记的下移**

(2)子节点具有覆盖标记,父节点具有加法标记,加法标记的下移**

如果遵循先加后覆盖,那么这个加法tag毫无意义,那么如果遵循先覆盖后加,那么(2)就可以成立,那么(1)为了满足条件,那么我们就需要一个设定,即覆盖标记下移时,要清空子节点的加法tag标记,这样(1)也成立了。

3.加法标签和乘法标签冲突:遵循原则先乘后加(分析详见下面对洛谷P3373的分析)

但反正核心思路就是

看看产生冲突困境的前一步情况是哪几种,模拟一下有什么处理方式以及应用什么样的优先原则,能够使得这种优先级体现在tag值本身上。

经典考察方式

1.区间求和

2.区间求最大值

3.区间查询特征对象个数

4.区间染色

【例】给定一个长度为n(n <= 100000)的木板,支持两种操作:
1、P a b c       将[a, b]区间段染色成c;
2、Q a b         询问[a, b]区间内有多少种颜色;
保证染色的颜色数少于30种。

其实这与状压dp思维并无二异,都是将某个状态的有无用01表示,在更新父节点无非就是时候用“|”来更新

5.区间k大数

【例】给定n(n <= 100000)个数的数组,然后m(m <= 100000)条询问,询问格式如下:
     1、l r k          询问[l, r]的第K大的数的值 

线段树的每个结点存的不只是区间端点,而是这个区间内所有的数,并且是按照递增顺序有序排列的,建树过程是一个归并排序的过程,从叶子结点自底向上进行归并。最推荐使用的是泛型容器set,multiset以及algorithm自带的归并算法set_union,不过要记得 set _ union归并的两个集合一定要升序排列,并且你要拿个有分配过内存的常规数组来存储合并结果,再把数组中的数倒回去

6.矩形面积并

【例】给定n(n <= 100000)个平行于XY轴的矩形,求它们的面积并。如图四-4-1所示。

这类二维的问题是用线段树来求解,核心思想是降维,将某一维套用线段树,另外一维则用来枚举。具体过程如下:

STEP1

拆:将所有矩形拆成两条垂直于x轴的线段,平行x轴的边可以舍去(也可以是y),如下图所示。

STEP2

将所有矩形端点的y坐标进行离散化处理(有些坐标可能很大而且不一定是整数),将原坐标映射成小范围的整数可以作为数组下标更方便计算。如图所示,蓝色数字表示的是离散后的坐标,即1、2、3、4分别对应原先的5、10、23、25。假设离散后的y方向的坐标个数为m,则y方向被分割成m-1个独立单元,下文称这些独立单元为“单位线段”,分别记为<1-2>、<2-3>、<3-4>。这些线段正是我们需要用线段树来维护的。

STEP3

定义矩形的两条垂直于x轴的边中x坐标较小的为入边,x坐标较大的为出边,入边权值为+1,出边权值为-1,并将所有的线段按照x坐标递增排序,第i条线段的x坐标记为x[i],这个数组是为了下面从左往右扫时更新各个单位线段对应的k[i]的

STEP4

开一个数组k[m-1],一一对应每一条单位线段,第i条单位线段的记为k[i]如图。

做这一步的目的是为了后面从左往右进行枚举扫描的时候,判断当前单位线段k[i]的矩形面积是否存在,如果说此时k[i]为非假,就说明至少有一条矩形的左边,也就说明它还没有碰到右边,那么显然这块面积是有效的。

那么更新它也就靠的是上文的x[i]

STEP5

接下来就是从左到右的扫描了。长的计算无非就是依靠线段树,宽已定,判断面积是否有效,累加即可。

有效性如图:红色、黄色、蓝色三个矩形分别是3对相邻线段间的矩形面积和,其中红色部分的y方向由<1-2>、<2-3>两个“单位线段”组成,黄色部分的y方向由<1-2>、<2-3>、<3-4>三个“单位线段”组成,蓝色部分的y方向由<2-3>、<3-4>两个“单位线段”组成。特殊的,在计算蓝色部分的时候,<1-2>部分的权值由于第3条线段的插入(第3条线段权值为-1)而变为零,所以此长度无效。
以上所有相邻线段之间的面积和就是最后要求的矩形面积并。

有一个点是很容易被忽视的,原因是如下图所示:

y方向上的矩形不一定是连续的!

下面谈谈一些细节:

1)存边的时候要附带的存+-1,这是为了当你把边从左往右排序完后初始化x[i],并且记录点每个点之间对于的位置dis[i],还有一个值得注意的点,就是可能存在某个矩形的右边和某个矩形的左边刚好重合了,那么你需要x[i],x[i+1]来分别处理他们(因为宽长是0,所以这样处理既可以更新到k[],又可以不计入面积),且一定是-1的那个在前。

2)线段树是以节点下标建立的,那么l==r无实际意义。我们在STEP2那里就就可以在O(n)的时间内递归构造初始的线段树。

3)从左往右进行扫描的时候,为了防止出现上面的绿图的那个情况,那么就要自下而上的去扫一下,如果if(k[up_bound])那么up_bound++,否则用线段树查询下(down_bound,up_bound),
乘上区间宽,累加,然后更新down_bound继续扫...

(板子有空写)

7.矩形周长并

(有空再填坑)

例题

1.洛谷板子P3372

(指针版)

#include<iostream>
#include<cstring>
#include<cstdlib>
using namespace std;
#define INF 1e10+5
#define MAXN 100005
#define MINN -105
typedef long long int LL;
int n,m;
LL sta[MAXN];
LL ans;
struct node
{
    node* leftc;//左孩子
    node* rightc;//右孩子
    node* father;//父节点(常规题可以不用)
    LL data;//数据域
    int l;//左端
    int r;//右端
    int lazy;//lazy-tag
    //这个构造写的很迷= =
    node(int a,LL ll,int rr,int b,node*c=NULL,node*d=NULL,node*e=NULL):
    data(a),l(ll),r(rr),lazy(b),father(c),leftc(d),rightc(e){}
};
//树根
node* head=new node(0,0,0,0);
//建树
void built(int l,int r,node* pos,node* fa)
{
    pos->father=fa;
    pos->l=l;
    pos->r=r;
    pos->lazy=0;
    if(l==r){pos->data=sta[l];return;}
    pos->leftc=new node(0,0,0,0);
    built(l,(l+r)/2,pos->leftc,pos);
    pos->rightc=new node(0,0,0,0);
    built((l+r)/2+1,r,pos->rightc,pos);
    pos->data=pos->leftc->data+pos->rightc->data;//回溯更新父节点
}
//向下pushdown,释放tag值
void pushdown(node* pos)
{
    if(pos->leftc!=NULL)pos->leftc->data+=(pos->leftc->r-pos->leftc->l+1)*pos->lazy,
        pos->leftc->lazy+=pos->lazy;
    if(pos->rightc!=NULL)pos->rightc->data+=(pos->rightc->r-pos->rightc->l+1)*pos->lazy,
        pos->rightc->lazy+=pos->lazy;
        pos->lazy=0;
}

//更新
void renew(int l,int r,int tag,node* pos)
{
    pushdown(pos);
    if(pos->l>r||pos->r<l)return;
    if(pos->l>=l&&pos->r<=r)
    {
        pos->data+=(pos->r-pos->l+1)*tag;
        pos->lazy=tag;
        return;
    }
    renew(l,r,tag,pos->leftc);
    renew(l,r,tag,pos->rightc);
    pos->data=pos->leftc->data+pos->rightc->data;
}
//查询
void check(int l,int r,node* pos)
{
    pushdown(pos);
    if(pos->l>r||pos->r<l)return;
    if(pos->l>=l&&pos->r<=r){ans+=pos->data;return;}
    check(l,r,pos->leftc);
    check(l,r,pos->rightc);
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        cin>>sta[i];
    built(1,n,head,NULL);
    int p,x,y,k;
    for(int i=0;i<m;i++)
    {
        cin>>p;
        if(p==1)
        {
            cin>>x>>y>>k;
            renew(x,y,k,head);
        }
        else
        {
            cin>>x>>y;
            ans=0;
            check(x,y,head);
            cout<<ans<<endl;
        }
    }
    return 0;
}

(简化版)

HDU1754

水题,区间求最大值,连续查询,小心毒瘤题干(多组数据...)

洛谷P3373

最为坑爹的地方就是在于它出现了两个更新操作,而且这两个更新操作都是必须使用懒惰标记的,否则会超时,那么我们就要正确处理这两个更新优先级的关系,也就是说到底是先乘后加,还是先加后乘?

那我们不妨先想一想,如果两个标记在同一个节点的时候,那么在产生这种冲突情况的上一步状态必定是

1)子节点具有加法标记,父节点具有乘法标记,乘法标记的下移

2)子节点具有乘法标记,父节点具有加法标记,加法标记的下移

那么对于这两种情况,而我们只能有一种处理方式,能使得从数值本身上能够体现出这种先后。

这个时候我们想起来乘法分配律

( a + b ) * c == a * c + b * c

对于a的值,本来是与b先进行运算的,但是在进行乘法分配了之后,a与c运算优先级更高了,正基于此,我们只要b在向下存储的时候做了*c的处理,每步运算先乘后加即可。

对于数据域的更新也是如此,即先乘后加。

所有点的初始化加法tag为0,乘法tag为1

AC记录 https://www.luogu.com.cn/record/31189187

猜你喜欢

转载自www.cnblogs.com/et3-tsy/p/12386701.html