- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
这里主要介绍树状数组套权值线段树的方法,毕竟基本上所有的树套树题都能用这种方法解,并且时间复杂度都是 \(n\times (logn)^2\).
这里有一道例题.
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
查询 \(k\) 在区间内的排名 。
查询区间内排名为 \(k\) 的值 。
修改某一位置上的数值 。
查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(x\),且最大的数,若不存在输出 -2147483647) 。
查询 \(k\) 在区间内的后继(后继定义为严格大于 \(x\),且最小的数,若不存在输出 2147483647) 。
第一行两个数 \(n,m\),表示长度为 \(n\) 的有序序列和 \(m\) 个操作.
第二行有 \(n\) 个数,表示有序序列.
下面有 \(m\) 行,\(opt\) 表示操作标号.
若 \(opt=1\),则为操作 \(1\),之后有三个数 \(l~r~k\),表示查询 \(k\) 在区间 \([l,r]\) 的排名.
若 \(opt=2\),则为操作 \(2\),之后有三个数 \(l~r~k\),表示查询区间 \([l,r]\) 内排名为 \(k\) 的数.
若 \(opt=3\),则为操作 \(3\),之后有两个数 \(pos~k\),表示将 \(pos\) 位置的数修改为 \(k\).
若 \(opt=4\),则为操作 \(4\),之后有三个数 \(l~r~k\),表示查询区间 \([l,r]\) 内 \(k\) 的前驱.
若 \(opt=5\),则为操作 \(5\),之后有三个数 \(l~r~k\),表示查询区间 \([l,r]\) 内 \(k\) 的后继.
对于操作 \(1,2,4,5\),各输出一行,表示查询结果.
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
2
4
3
4
9
看完题目后可以发现这是一道树套树,然后下文主要讲解如何使用这棵树套树.
顾名而思义,就是用树状数组的方式来维护权值线段树(动态开点),我们对于上述的 \(5\) 个操作分别来看一下如何实现.
操作 \(1\) 查询 \(l\sim r\) 中 \(k\) 的排名,我们会只放在权值线段树上的做法,这里就是会多维护 \(2\) 个数组,就是和普通的树状数组一样,将每一次的 \(l,r\) 都存下来,然后在查询中用 \(r\) 的总和减去 \(l\) 的即可,记住在往另一个地方递归时要更新这两个数组.
int rk1(int l,int r,int k) {
if(l==r) {
return 1;
}
int mid=(l+r)/2,sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;//和普通的树状数组相同
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(mid>=k) {
rep(i,1,cs) s[i]=tr[s[i]].l;//向那一边递归也要将 l,r 数组改一下
rep(i,1,cp) p[i]=tr[p[i]].l;
return rk1(l,mid,k);
}else{
rep(i,1,cs) s[i]=tr[s[i]].r;
rep(i,1,cp) p[i]=tr[p[i]].r;
return sum+rk1(mid+1,r,k);
}
}
l--;//用 r 的减去 l-1 的就为 l~r 中的
cs=cp=false;//清空
for(;l;l-=lowbit(l)) s[++cs]=rt[l];//与树状数组模板一样
for(;r;r-=lowbit(r)) p[++cp]=rt[r];
对于操作二,其实和 \(1\) 的实现过程一样,就是在普通权值线段树上加上了 \(l,r\) 数组的改变而已.
int Ans(int l,int r,int k) {
if(l==r) return l;
int mid=(l+r)>>1;
int sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;//同理
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;//同理
if(k<=sum) {
rep(i,1,cs) s[i]=tr[s[i]].l;//改变
rep(i,1,cp) p[i]=tr[p[i]].l;
return Ans(l,mid,k);
}else {
rep(i,1,cs) s[i]=tr[s[i]].r;//改变
rep(i,1,cp) p[i]=tr[p[i]].r;
return Ans(mid+1,r,k-sum);
}
}
l--;//用 r 的减去 l-1 的就为 l~r 中的
cs=cp=false;//清空
for(;l;l-=lowbit(l)) s[++cs]=rt[l];//与树状数组模板一样
for(;r;r-=lowbit(r)) p[++cp]=rt[r];
操作三是最简单的直接修改即可,这里可以直接结合树状数组的方式直接将每一个都 modify 一下即可.
void modify(int &u,int l,int r,int k,int cnt) {
if(!u) u=++idx;//动态开点
tr[u].sum+=cnt;//加上
if(l==r) return;
int mid=(l+r)/2;
if(mid>=k) modify(tr[u].l,l,mid,k,cnt);
else modify(tr[u].r,mid+1,r,k,cnt);
}
in(l),in(k);
for(int i=l;i<=n;i+=lowbit(i)) modify(rt[i],0,Max,a[l],-1);//先减后加
a[l]=k;
for(int i=l;i<=n;i+=lowbit(i)) modify(rt[i],0,Max,a[l],1);
操作四,这里我不会直接转移所以用了一下二分一下排名,直接看排名为 \(mid\) 的数是否小于 \(k\) 即可.
in(l),in(r),in(k);
int L=1,R=r-l+1,res=false;
while(L<=R) {
int mid=L+R>>1;
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
if(Ans(0,Max,mid)<k) res=mid,L=mid+1;
else R=mid-1;
}
if(!res) {
cout<<"-2147483647\n";
continue;
}
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
cout<<Ans(0,Max,res)<<endl;
操作五同理就是将小于改为大于即可.
#include <bits/stdc++.h>
using namespace std;
#define rep(i,x,y) for(register int i=x;i<=y;i++)
#define rep1(i,x,y) for(register int i=x;i>=y;--i)
#define in(x) scanf("%d",&x)
#define ll long long
#define fire signed
#define il inline
il void print(int x) {
if(x<0) putchar('-'),x=-x;
if(x>=10) print(x/10);
putchar(x%10+'0');
}
int T;
const int N=5e4+10;
struct node{
int l,r;
int sum;
}tr[N*2*16*16];
const int Max=1e8+1;
int n,m,idx;
int cp,cs;
int p[N],s[N];
void modify(int &u,int l,int r,int k,int cnt) {
if(!u) u=++idx;
tr[u].sum+=cnt;
if(l==r) return;
int mid=(l+r)/2;
if(mid>=k) modify(tr[u].l,l,mid,k,cnt);
else modify(tr[u].r,mid+1,r,k,cnt);
}
int rt[N],a[N];
int lowbit(int x) {
return x&-x;
}
int rk(int l,int r,int k) {
if(l==r) {
int sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(!sum) sum=1;
return sum;
}
int mid=(l+r)/2,sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(mid>=k) {
rep(i,1,cs) s[i]=tr[s[i]].l;
rep(i,1,cp) p[i]=tr[p[i]].l;
return rk(l,mid,k);
}else{
rep(i,1,cs) s[i]=tr[s[i]].r;
rep(i,1,cp) p[i]=tr[p[i]].r;
return sum+rk(mid+1,r,k);
}
}
int rk2(int l,int r,int k) {
if(l==r) {
int sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
return sum;
}
int mid=(l+r)/2,sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(mid>=k) {
rep(i,1,cs) s[i]=tr[s[i]].l;
rep(i,1,cp) p[i]=tr[p[i]].l;
return rk2(l,mid,k);
}else{
rep(i,1,cs) s[i]=tr[s[i]].r;
rep(i,1,cp) p[i]=tr[p[i]].r;
return sum+rk2(mid+1,r,k);
}
}
int rk1(int l,int r,int k) {
if(l==r) {
return 1;
}
int mid=(l+r)/2,sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(mid>=k) {
rep(i,1,cs) s[i]=tr[s[i]].l;
rep(i,1,cp) p[i]=tr[p[i]].l;
return rk1(l,mid,k);
}else{
rep(i,1,cs) s[i]=tr[s[i]].r;
rep(i,1,cp) p[i]=tr[p[i]].r;
return sum+rk1(mid+1,r,k);
}
}
int Ans(int l,int r,int k) {
if(l==r) return l;
int mid=(l+r)>>1;
int sum=false;
rep(i,1,cs) sum-=tr[tr[s[i]].l].sum;
rep(i,1,cp) sum+=tr[tr[p[i]].l].sum;
if(k<=sum) {
rep(i,1,cs) s[i]=tr[s[i]].l;
rep(i,1,cp) p[i]=tr[p[i]].l;
return Ans(l,mid,k);
}else {
rep(i,1,cs) s[i]=tr[s[i]].r;
rep(i,1,cp) p[i]=tr[p[i]].r;
return Ans(mid+1,r,k-sum);
}
}
void solve() {
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
in(n),in(m);
rep(i,1,n) {
in(a[i]);
for(int j=i;j<=n;j+=lowbit(j)) modify(rt[j],0,Max,a[i],1);
}
while(m--) {
int opt;
int l,r,k;
in(opt);
if(opt==1) {
in(l),in(r),in(k);
cs=cp=false;
l--;
for(;l;l-=lowbit(l)) s[++cs]=rt[l];
for(;r;r-=lowbit(r)) p[++cp]=rt[r];
cout<<rk1(0,Max,k)<<endl;
}else if(opt==2){
in(l),in(r),in(k);
cs=cp=false;
l--;
for(;l;l-=lowbit(l)) s[++cs]=rt[l];
for(;r;r-=lowbit(r)) p[++cp]=rt[r];
cout<<Ans(0,Max,k)<<endl;
}else if(opt==3) {
in(l),in(k);
for(int i=l;i<=n;i+=lowbit(i)) modify(rt[i],0,Max,a[l],-1);
a[l]=k;
for(int i=l;i<=n;i+=lowbit(i)) modify(rt[i],0,Max,a[l],1);
}else if(opt==4) {
in(l),in(r),in(k);
int L=1,R=r-l+1,res=false;
while(L<=R) {
int mid=L+R>>1;
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
if(Ans(0,Max,mid)<k) res=mid,L=mid+1;
else R=mid-1;
}
if(!res) {
cout<<"-2147483647\n";
continue;
}
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
cout<<Ans(0,Max,res)<<endl;
}else {
in(l),in(r),in(k);
int L=1,R=r-l+1,res=false;
while(L<=R) {
int mid=L+R>>1;
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
if(Ans(0,Max,mid)>k) res=mid,R=mid-1;
else L=mid+1;
}
if(res==0) cout<<"2147483647\n";
else {
cs=cp=false;
for(int i=l-1;i;i-=lowbit(i)) s[++cs]=rt[i];
for(int i=r;i;i-=lowbit(i)) p[++cp]=rt[i];
cout<<Ans(0,Max,res)<<endl;
}
}
}
return;
}
fire main() {
T=1;
while(T--) {
solve();
}
return false;
}
最后此篇关于树套树的文章就讲到这里了,如果你想了解更多关于树套树的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
关于 B 树与 B+ 树,网上有一个比较经典的问题:为什么 MongoDb 使用 B 树,而 MySQL 索引使用 B+ 树? 但实际上 MongoDb 真的用的是 B 树吗?
如何将 R* Tree 实现为持久(基于磁盘)树?保存 R* 树索引或保存叶值的文件的体系结构是什么? 注意:此外,如何在这种持久性 R* 树中执行插入、更新和删除操作? 注意事项二:我已经实现了一个
目前,我正在努力用 Java 表示我用 SML 编写的 AST 树,这样我就可以随时用 Java 遍历它。 我想知道是否应该在 Java 中创建一个 Node 类,其中包含我想要表示的数据,以及一个数
我之前用过这个库http://www.cs.umd.edu/~mount/ANN/ .但是,它们不提供范围查询实现。我猜是否有一个 C++ 范围查询实现(圆形或矩形),用于查询二维数据。 谢谢。 最佳
在进一步分析为什么MySQL数据库索引选择使用B+树之前,我相信很多小伙伴对数据结构中的树还是有些许模糊的,因此我们由浅入深一步步探讨树的演进过程,在一步步引出B树以及为什么MySQL数据库索引选择
书接上回,今天和大家一起动手来自己实现树。 相信通过前面的章节学习,大家已经明白树是什么了,今天我们主要针对二叉树,分别使用顺序存储和链式存储来实现树。 01、数组实现 我们在上一节中说过,
书节上回,我们接着聊二叉树,N叉树,以及树的存储。 01、满二叉树 如果一个二叉树,除最后一层节点外,每一层的节点数都达到最大值,即每个节点都有两个子节点,同时所有叶子节点都在最后一层,则这个
树是一种非线性数据结构,是以分支关系定义的层次结构,因此形态上和自然界中的倒挂的树很像,而数据结构中树根向上树叶向下。 什么是树? 01、定义 树是由n(n>=0)个元素节点组成的
操作系统的那棵“树” 今天从一颗 开始,我们看看如何从小树苗长成一颗苍天大树。 运转CPU CPU运转起来很简单,就是不断的从内存取值执行。 CPU没有好好运转 IO是个耗费时间的活,如果CPU在取值
我想为海洋生物学类(class)制作一个简单的系统发育树作为教育示例。我有一个具有分类等级的物种列表: Group <- c("Benthos","Benthos","Benthos","Be
我从这段代码中删除节点时遇到问题,如果我插入数字 12 并尝试删除它,它不会删除它,我尝试调试,似乎当它尝试删除时,它出错了树的。但是,如果我尝试删除它已经插入主节点的节点,它将删除它,或者我插入数字
B+ 树的叶节点链接在一起。将 B+ 树的指针结构视为有向图,它不是循环的。但是忽略指针的方向并将其视为链接在一起的无向叶节点会在图中创建循环。 在 Haskell 中,如何将叶子构造为父内部节点的子
我在 GWT 中使用树控件。我有一个自定义小部件,我将其添加为 TreeItem: Tree testTree = new Tree(); testTree.addItem(myWidget); 我想
它有点像混合树/链表结构。这是我定义结构的方式 struct node { nodeP sibling; nodeP child; nodeP parent; char
我编写了使用队列遍历树的代码,但是下面的出队函数生成错误,head = p->next 是否有问题?我不明白为什么这部分是错误的。 void Levelorder(void) { node *tmp,
例如,我想解析以下数组: var array1 = ["a.b.c.d", "a.e.f.g", "a.h", "a.i.j", "a.b.k"] 进入: var json1 = { "nod
问题 -> 给定一棵二叉树和一个和,确定该树是否具有从根到叶的路径,使得沿路径的所有值相加等于给定的和。 我的解决方案 -> public class Solution { public bo
我有一个创建 java 树的任务,它包含三列:运动名称、运动类别中的运动计数和上次更新。类似的东西显示在下面的图像上: 如您所见,有 4 种运动:水上运动、球类运动、跳伞运动和舞蹈运动。当我展开 sk
我想在 H2 数据库中实现 B+ Tree,但我想知道,B+ Tree 功能在 H2 数据库中可用吗? 最佳答案 H2 已经使用了 B+ 树(PageBtree 类)。 关于mysql - H2数据库
假设我们有 5 个字符串数组: String[] array1 = {"hello", "i", "cat"}; String[] array2 = {"hello", "i", "am"}; Str
我是一名优秀的程序员,十分优秀!