首页 > 其他分享 >可持久化线段树(主席树)

可持久化线段树(主席树)

时间:2024-11-11 15:10:26浏览次数:1  
标签:持久 rs int 线段 tr 端点 区间 主席

  • 主席树作为最常用的可持久化数据结构,广泛运用与各种区间、树上问题的在线求解已经对DP的优化上。这里主要讨论其单纯作为数据结构的应用。

P1972 [SDOI2009] HH的项链

  • 这是一道极其经典的题——静态区间种类数,其变体非常多,树上的,待修的,强制在线的等等。
    这题做法也很多样,离线后树状数组、线段树以及分块都可以做。不过既然是主席树,那就要讲最正统的在线的主席树写法。

思路

  • 主席树是如何解决“种类数”这一问题的呢?
    首先,主席树维护区间问题与一般的线段树可能有些不同。普通线段树是叶子节点的值对应区间上的相应位置店的值。
    而主席树一般而言不这么干。主席树的叶子节点一般是一个数轴,维护每种数的信息,在本质上是一棵权值线段树。
    当然这也不绝对,还是要具体情况具体分析。

  • 如果考虑直接用主席树维护每种数的出现次数,这是没办法做到的。
    因此考虑改变一下维护的东西。有一个很常见的trick是,对于类似种类数的问题,维护与其数值相等的下一个数出现的位置。
    想一下这个东西有什么性质。如果对于一个询问区间,当且仅当这个区间中的某一种数下一个数出现的位置比询问区间的右端点大,这种数就能对答案产生1的贡献。

  • 因此直接维护对于每个数下一个与其数值相等的数的位置,每次查询的就是以r和l-1为根的线段树区间右端点到n+1(为什么是n+1一会说)的值

小细节

  1. 首先仍然是主席树的套路。对于原序列上的每个点,都建立线段树记录前缀和。对于上面我们维护的信息显然有可差分性,因此对于询问直接正常差分做就可以了。

  2. 为什么右端点是n+1呢?因为对于一些数,其之后就没有出现过了,因此我们就直接将其的值赋为n+1,因此值域就是1~n+1了

  3. 主席树的空间复杂度需要特别注意。每次新插入一个节点时最多新建 \(log_n\) 个节点,因此一般而言开n的20到30倍左右。我为了保险开的25倍

code

(略有压行)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e6+7;
int n,m,a[N],tr[N*25],rt[N],idcnt=0,lst[N],val[N],ls[N*25],rs[N*25];
inline void push_up(int u){tr[u]=tr[ls[u]]+tr[rs[u]];}
inline int insert(int last,int l,int r,int w)
{
	int u=++idcnt,mid=(l+r)>>1;
	ls[u]=ls[last],rs[u]=rs[last],tr[u]=tr[last];
	if(l==r){tr[u]+=1;return u;}
	if(w<=mid) ls[u]=insert(ls[last],l,mid,w);
	else rs[u]=insert(rs[last],mid+1,r,w);
	push_up(u);return u;
}
inline int query(int u1,int u2,int l,int r,int ql,int qr)
{
	if(l>=ql&&r<=qr) return tr[u1]-tr[u2];
	int mid=(l+r)>>1,ans=0;
	if(ql<=mid) ans+=query(ls[u1],ls[u2],l,mid,ql,qr);
	if(qr>mid) ans+=query(rs[u1],rs[u2],mid+1,r,ql,qr);
	return ans;
}
int main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n;rt[0]=0;
	for(int i=1;i<=n;i++) cin>>a[i],lst[a[i]]=n+1;
	for(int i=n;i>=1;i--) val[i]=lst[a[i]],lst[a[i]]=i;
	for(int i=1;i<=n;i++) rt[i]=insert(rt[i-1],1,n+1,val[i]);
	cin>>m;
	for(int i=1,l,r;i<=m;i++) {cin>>l>>r;cout<<query(rt[r],rt[l-1],1,n+1,r+1,n+1)<<'\n';}
	return 0;
}

P4137 Rmq Problem / mex

  • 此题也是非常经典的——静态区间求mex。mex指是指定区间最小的没出现过的自然数(包括0)

思路

  • 还是套路地对于每个点建立线段树作为前缀,考虑如何求mex
    由于我们求的是前缀,因此我们肯定是基于查询区间右端点为基础去查询的。
    对于这种“某数没出现过”的题,我们将维护的值设为其最后一次出现的位置,这样主席树仍然本质上维护的是前缀,即从头开始的、所有数出现的最后的位置。没出现过的数设为0

  • 而从叶子节点向上合并统计的时候,我们让非叶子节点记录其管辖的区域内所有点出现的最前面的位置。
    这样统计的原因是我们可以以查询区间左端点直接查找。如果区间出现的最左边的数比查询区间左端点小那就向左搜,反之向右搜。

小细节

  1. 这道题的数值范围很大,比n大得多,因此有很多人使用了离散化。然而其实并不用。显然最终的答案不会大于n+1(给出的是n的排列)
    因此对于比n大的数直接赋为n+1就好了。
  2. 空间复杂度不多赘述,但还是要注意。
  3. 注意,由于主席树是动态开点的,因此要注意查询时的边界条件。如果这个点从来没有被创建或访问过那就要直接返回其所代表区间的左端点
    即这个区间里没有一个点,那这个区间的左端点就是最小的没出现过的数(请注意这是权值线段树)

code

(还是小压行)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7;int M;
int n,m,a[N],loc[N<<5],ls[N<<5],rs[N<<5],cnt=0,rt[N];
void push_up(int u){loc[u]=min(loc[ls[u]],loc[rs[u]]);}
int modify(int last,int l,int r,int x,int w)
{
	int u=++cnt;ls[u]=ls[last],rs[u]=rs[last];
	if(l==r) {loc[u]=w;return u;}
	int mid=(l+r)>>1;
	if(x<=mid) ls[u]=modify(ls[last],l,mid,x,w);
	else rs[u]=modify(rs[last],mid+1,r,x,w);
	push_up(u);return u;
}
int build(int l,int r)
{
	int u=++cnt;int mid=(l+r)>>1;
	if(l==r) {loc[u]=0;return u;}
	ls[u]=build(l,mid),rs[u]=build(mid+1,r);
	push_up(u);return u;
}
int query(int u,int l,int r,int x)
{
	if(!u||l==r) return l;
	int mid=(l+r)>>1;
	if(loc[ls[u]]<x) return query(ls[u],l,mid,x);
	else return query(rs[u],mid+1,r,x);
}
int main()
{
	ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	cin>>n>>m;M=n+1;rt[0]=build(0,M);
	for(int i=1;i<=n;i++){cin>>a[i];if(a[i]>n) a[i]=M;rt[i]=modify(rt[i-1],0,M,a[i],i);}
	for(int i=1,l,r;i<=m;i++){cin>>l>>r;cout<<query(rt[r],0,M,l)<<'\n';}
	return 0;
}

标签:持久,rs,int,线段,tr,端点,区间,主席
From: https://www.cnblogs.com/allforgod/p/18539737

相关文章

  • 【一步步开发AI运动小程序】二十一、如果将AI运动项目配置持久化到后端?
    说明:本文所涉及的AI运动识别、计时、计数能力,都是基于云智「Ai运动识别引擎」实现。云智「Ai运动识别」插件识别引擎,可以为您的小程序或UniAPP赋于原生、本地、广覆盖、高性能的人体识别、姿态识别、10余种常见的运动计时、计数识别及自定义扩展运动识别能力。完善的文档、Demo......
  • 【模板】可持久化线段树 2(洛谷P3834)
    #include<bits/stdc++.h>#defineendl'\n'usingll=longlong;typedefunsignedlonglongull;usingnamespacestd;voidGordenGhost();constexprintN=2e5+7;intn,m,a[N],b[N];introot[N],tot;//根节点所有节点个数intls[N*40],rs[N*40],sum......
  • 安卓手机剪贴板数据持久化(MacroDroid教程)
    本教程将使用MacroDroidApp实现对安卓手机上复制过数据进行持久化保存,并能快速查看已保存的内容。MacroDroid软件介绍MacroDroid是一款功能强大的自动化应用程序,可帮助用户通过创建宏(macros)来自动化他们的Android设备上的各种任务和操作。用户可以使用MacroDroid应用......
  • 可持久化线段树
    少写了一点,可持久化的好处就是可以用较低的代价去得到可以变换版本这一功能。可持久化线段树(主席树)带注释的代码/*注意,可持久化线段树很难支持区间修改,一般涉及区间修改的时候不用单点修改是可以的一样,直接选这题不大好,看下面的通用模版,具有通......
  • 六、MyBatis-Plus高级用法(1):最优化持久层开发
    一、MyBatis-Plus快速入门1.1简介课程版本:3.5.3.1MyBatis-Plus......
  • Redis中的持久化
    什么是Redis持久化?        Redis是一个内存数据库,也就是说它主要把数据存储在内存中,这样可以实现非常高的读写速度。通常,内存数据库是非常快速且高效的,但它也有一个很大的问题:数据丢失的风险。因为当Redis服务关闭或系统崩溃时,所有存储在内存中的数据都将丢失。......
  • 031集——获取外轮廓(只支持线段多段线)(CAD—C#二次开发入门)
    此版本跟007集相比,增加了个识别断线头的功能,即原始图形中线段可不闭合。usingAutodesk.AutoCAD.DatabaseServices;usingAutodesk.AutoCAD.Geometry;usingSystem;usingSystem.Collections.Generic;usingSystem.Diagnostics;usingSystem.Linq;usingSystem.Text;u......
  • 线段树知识乱讲
    前言算法竞赛题目考察的是选手对于数据结构的选取与算法的巧妙结合,而数据结构中线段树扮演一个至关重要的角色,而近期(CSP结束)在hfu的安排下我们需要自己弄一周的ds,所以就有了这篇奇妙的博客。线段树基础知识在我看来,线段树其实就是在数组的基础上添加了一些额外的点,这些点用......
  • zkw 线段树-原理及其扩展
    前言许多算法的本质是统计。线段树用于统计,是沟通原数组与前缀和的桥梁。《统计的力量》清华大学-张昆玮关于线段树前置知识:线段树OIWiki。线段树是一种专门维护区间问题的数据结构。线段树对信息进行二进制化处理并在树形结构上维护,以此让处理速度达到\(O(\log{n})\)......
  • 线段树与树状数组
    线段树与树状数组都是十分经典的数据结构,其实能用树状数组解决的问题也都能用线段树解决,但线段树相比于树状数组常数较大。单点修改区间查询线段树做法,树状数组做法,其实单纯实现这个还是用树状数组较好(毕竟常数小还好写)区间修改区间查询只有区间加树状数组做法,线段树做法既......