首页 > 其他分享 >线段树的一些延伸

线段树的一些延伸

时间:2023-08-09 22:13:54浏览次数:37  
标签:return int 线段 mid inline 延伸 一些 void

一.动态开点线段树

简介

虽然思路简单,但对于一个习惯数组写法的人,这是一个比较难受的东西。

动态开点一般是用来解决空间上的问题的。

一般来说,普通的线段树是直接将一颗完整的线段建出来,但如碰到数据范围大或卡空间的时候,我们就只能在我们需要的时候再建,这个就叫做动态开点。(类似于 trie)

处理方法

  • 结构体

动态开点用数组是极不方便的,所以采用结构体写法。(本人真的很不习惯)

struct Tree
{
    int sum;
    int l,r;
}t[MAXN<<5];
  • 上传
inline void pushup(int p)
{
    t[p].sum=t[t[p].l].sum+t[t[p].r].sum;
    return;
}
  • 下传

由于跑到的节点可能没建过,所以要新建。

inline void pushdown(int p)
{
    if(!t[p].l) t[p].l=++tot;
    if(!t[p].r) t[p].r=++tot;
    return;
}
  • 单点修改
inline void change(int p,int l,int r,int x,int k)
{
    if(l==r) {t[p].sum+=k;return;}
    int mid=(l+r-1)>>1; pushdown(p);
    if(x<=mid) change(t[p].l,l,mid,x,k);
    else change(t[p].r,mid+1,r,x,k);
    pushup(p); return;
}
  • 求和
inline int ask(int p,int l,int r,int a,int b)
{
    if(l>b || r<a) return 0;
    if(l>=a && r<=b) return t[p].sum;
    int mid=(l+r-1)>>1,ans=0; pushdown(p);
    if(a<=mid) ans+=ask(t[p].l,l,mid,a,b);
    if(b>mid) ans+=ask(t[p].r,mid+1,r,a,b);
    return ans;
}

二.权值线段树

简介

对于值域建立的线段树,用于统计区间内某个数出现次数。

支持维护同一个动态区间第 \(k\) 小(反之同理),支持单点修改。

修改与查询的复杂度均为 \(O(\log n)\)。

处理方法

  • 建树

建树只需维护左右区间,无需赋值。

1. 用每个节点维护原序列中的值;
2. 记录每个区间每个数的出现次数;

inline void build(int p,int l,int r)
{
    t[p].l=l,t[p].r=r;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
    return;
}

特别地,因为权值线段树又名值域线段树,所以建树是基于值域建的。

如:

题目规定:\(a_i\leqslant 10^5\)

那么建树操作便为 build(1,1,100000)

  • 修改

权值线段树在修改中可以维护区间第 \(k\) 小于前 \(k\) 小的和,故需要统计一下。

inline void change(int p,int k)
{
    t[p].val+=k,t[p].num++;
    if(t[p].l==t[p].r) return;
    if(k>=t[p<<1|1].l) change(p<<1|1,k);
    else change(p<<1,k);
    return;
}
  • 查询

有了上面统计的那些信息,区间求值就很容易了。

区间前 \(k\) 小之和

inline int ask1(int p,int k)
{
    if(t[p].l==t[p].r) return t[p].val/t[p].num*k;
    if(k>t[p<<1].num) return t[p<<1].val+ask1(p<<1|1,k-t[p<<1].num);
    else return ask1(p<<1,k);
}

区间第 \(k\) 小

inline int ask2(int p,int k)
{
    if(t[p].l==t[p].r) return t[p].val/t[p].num;
    if(k>t[p<<1].num) return ask2(p<<1|1,k-t[p<<1].num);
    else return ask2(p<<1,k);
}

三.主席树

简介

又名可持久化权值线段树,用于动态维护任意区间第 \(k\) 大。

支持单点修改和区间查询。

处理方法

主席树的思想是对每一个前缀均建立一颗权值线段树。

  • 初始化

我们发现,由于空间复杂度的问题,我们需要动态开点,所以就无法直接算出一个节点的左右儿子,所以都要记录一下。

需要注意的是,主席树一般要开32倍空间。

\(rt_i\) 为版本 \(i\) 的标号。

int id[MAXN];

struct Tree
{
    int l,r,val;
}e[MAXN<<5];
  • 建树

我们无法直接算出左右儿子编号,所以建树可以只用统计 \(id\) 数组。

inline void build(int &p,int l,int r)
{
    p=++cnt;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(t[p].l,l,mid);
    build(t[p].r,mid+1,r);
    return;
}

\(cnt\) 表示动态开点的编号。

  • 修改

与权值线段树唯一不同的是要再开一颗线段树。

inline int change(int p,int l,int r,int k)
{
    int rt=++cnt;
    t[rt]=t[p],t[rt].val++;
    if(l==r) return rt;
    int mid=(l+r)>>1;
    if(k<=mid) t[rt].l=change(t[p].l,l,mid,k);
    else t[rt].r=change(t[p].r,mid+1,r,k-x);
    return rt;
}
  • 查询

由于每一颗线段树的结构相同,具有可加减性。

并且我们建立的是关于前缀的线段树,所以我们可以用前缀和的思想。

inline int ask(int l,int r,int a,int b,int k)
{
    int ans=0,mid=(l+r)>>1;
    int x=t[t[b].l].val-t[t[a].l].val;
    if(l==r) return l;
    if(k<=x) ans=ask(l,mid,t[a].l,t[b].l,k);
    else ans=ask(mid+1,r,t[a].r,t[b].r,k);
    return ans;
}
  • 离散化

考虑到空间问题,我们需要离散化。

inline void dist()
{
    sort(b+1,b+n+1);
    m=unique(b+1,b+n+1)-b-1;
    build(id[0],1,m);
    for(int i=1;i<=n;i++)
    {
        int p=lower_bound(b+1,b+m+1,a[i])-b;
        id[i]=change(id[i-1],1,m,p);
    }
    return;
}

到这里,主席树的模板就没了,但还有一些操作需要自己去探索。

例题:

四.线段树合并

思想

前置知识:动态开点线段树权值线段树

顾名思义,就是建立一颗新的线段树,保存原有两颗线段树的信息。

这个思想比较简单,假设我们现在合并到了两棵线段树 \(a,b\) 的 \(p\) 位置,那么:

1.如果 \(a\) 有 \(p\) 位置,\(b\) 没有,那么新的线段树 \(p\) 位置赋成 \(a\),返回;
2.如果 \(b\) 有 \(p\) 位置,\(a\) 没有,赋成 \(b\),返回;
3.如果此时已经合并到两棵线段树的叶子节点了,就把 \(b\) 在 \(p\) 的值加到 \(a\) 上,把新线段树上的 \(p\) 位置赋成 \(a\),返回;
4.递归处理左子树,递归处理右子树;
5.用左右子树的值更新当前节点;
6.将新线段树上的 \(p\) 位置赋成 \(a\),返回;

处理方法

  • 合并

线段树合并的核心操作。

inline int merge(int a,int b,int l,int r)
{
    if(!a) return a;
    if(!b) return b;
    if(l==r) return a;
    int mid=(l+r)>>1;
    t[a].l=merge(t[a].l,t[b].l,l,mid);
    t[a].r=merge(t[a].r,t[b].r,mid+1,r);
    pushup(a); return a;
}

假设要插入的点数为 \(n\),那么时空复杂度均为 \(\Theta(n\log n)\)

标签:return,int,线段,mid,inline,延伸,一些,void
From: https://www.cnblogs.com/code-ac/p/17618085.html

相关文章

  • 【总结一下|LaTex语法】一些常用的LaTex语法小知识
    文章目录快速检索矩阵语法示例上标下标求和分数希腊字母语法示例大括号算式标签字母头上横线字母头上加^号字母头上加波浪线字母头上加点输入中括号大于等于小于等于...字母上添加波浪线向量积分符号举例波浪线整数、实数、自然数子集、真子集、空集箭头空格、缩进加粗绝对值上括......
  • 关于判定性问题和最优化问题的联系的一些思考
    关于判定性问题和最优化问题的联系的一些思考引入判定性问题判定性问题是指在某些约束条件下,给定命题判断是否成立的一系列问题。比如:给定一张无向图\(G\),判断图是否连通。答案只可能是两种:连通和不连通。这就是一个判定性问题。最优化问题最优化问题(optimizationpr......
  • Qt多语言切换时,QComboBox引起的一些问题
    板子Qt版本为5.9.5PC开发环境Qt版本为5.12.2界面有2个QComboBox,其中一个是用于切换语言,最开始使用的是voidcurrentIndexChanged(intindex)信号,多语言切换代码大致如下://绑定切换信号connect(ui->cbox_lang,QOverload<int>::of(&QComboBox::currentIndexChanged),this,&Fo......
  • ABC245E Wrapping Chocolate [线段树二分]
    也许更好的阅读体验\(\mathcal{Description}\)\(n\)个物品有长和宽,\(m\)个盒子也有长和宽,一个盒子最多可以装一个物品,问\(n\)个物品能否都放进盒子,物品和盒子不能旋转\(\mathcal{Solution}\)先离散化长和宽,将物品和盒子按照长从大到小排序考虑到当前物品时将所有长大于等于当......
  • Angular FormControl value属性的一些事
    背景:一个输入校验,允许输入多行,每一行是ip或网段。写了个校验,将其按行拆分后单独校验。1. FormControl无法深复制使用JSON.parse(JSON.stringify(control))进行简单深复制报错,因为不是json类型;使用deepClone进行递归深复制,直接栈溢出。考虑到代码的健壮性,已经有单独校验......
  • 线段树补充
    线段树补充线段树维护矩阵和矩阵快速幂和普通快速幂同理intM;structmatrix{ llx[M+1][M+1]; matrix(){ memset(x,0,sizeof(x)); }};matrixmultiply(matrix&a,matrix&b){ matrixc; for(inti=1;i<=M;i++) for(intj=1;j<=M;j++) for(intk=1;k<=......
  • 关于菜鸡学习RHEL8的一些小笔记--->防火墙
    #如果说SELINUX是对内管控应用程序的安全,那么防火主要是对外进行管理防火墙:Linux的内核中包含了netfilter,netfilter主要是对流量操作的一个框架,其中包括数据包的过滤,网络地址转换和端口转发等;在rhel8中内核还包含nftables,这是一个较新的数据包分类和过滤的子系统,在netfilter的......
  • 在最新更新的 Windows 系统中使用 .net 程序调用一些 https 接口时出现错误:请求被中止
    这是因为出于安全原因,新更新的系统中会默认禁用一些已经过时不安全的加密协议如:SSL3.0、TLS1.0、TLS1.1等但并不是所有接口服务器都已经更新支持了更新的协议所以在确认安全的情况下,可以将这些旧的协议再次启用,以达到兼容旧接口调用的目的方法1:注意:这个修改会在系统全局......
  • 云存储---ceph简介架构原理和一些基本概念
    Ceph简介Ceph是一个分布式存储系统,提供对象,块和文件存储,是一个免费开源软件的存储解决方案,可以部署于普通的x86兼容服务器上,可用于解决统一存储的io问题。Ceph诞生于2004年,最早是SageWeil一项关于存储系统的PhD研究项目,致力于开发下一代高性能分布式文件系统的项目。随着云计算的发......
  • 云计算云存储的一些基本概念
    我们在学习云计算和云存储之前,需要先了解一些很常见的基本概念,否则在学习过程中和选型时会比较晕。云计算的三种服务模式:IaaS,PaaS和SaaS云的分层任何一个在互联网上提供其服务的公司都可以叫做云计算公司。其实云计算分几层的,分别是Infrastructure(基础设施)-as-a-Service,Platform(平......