首页 > 其他分享 >树套树

树套树

时间:2024-04-07 18:33:07浏览次数:13  
标签:树套 int sum tr mid cs cp

树套树

这里主要介绍树状数组套权值线段树的方法,毕竟基本上所有的树套树题都能用这种方法解,并且时间复杂度都是 \(n\times (logn)^2\)。

思路

这里有一道例题。

【模板】树套树

题目描述

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:

  1. 查询 \(k\) 在区间内的排名

  2. 查询区间内排名为 \(k\) 的值

  3. 修改某一位置上的数值

  4. 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(x\),且最大的数,若不存在输出 -2147483647

  5. 查询 \(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\),各输出一行,表示查询结果。

样例 #1

样例输入 #1

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

样例输出 #1

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;
    
  • 操作五同理就是将小于改为大于即可。

标签:树套,int,sum,tr,mid,cs,cp
From: https://www.cnblogs.com/highkj/p/18119667

相关文章

  • [DS 小计] 树套树
    笔者很菜,只会最简单的树状数组套权值线段树。不是,这玩意不就套娃吗,真ex啊题目简要:求\(x\)排名求排名为\(x\)的数求\(x\)前驱后继我们学了权值动态开点线段树就知道这些问题乱写就行了。但是套上\([l,r]\)区间呢,无修呢?我们会主席树这些乱写就行了。但是套上有......
  • CF19D(树套树)
    一道非常有意思的树套树。一眼一个空间\(n\logn\)时间\(n\log^{2}n\)的树套树,发现过不了。考虑优化。我们发现在中间线段树的地方可以不用平衡树存下来,只记最大值即可。然后我们对于每个横坐标开一颗fhq,然后分出\(\logn\)段区间,这些区间的最大值大于给定点的纵坐标。然后用......
  • 树套树从入门到去世
    如何实现数据结构的嵌套?首先我们知道,单个数据结构是对一些存有某些信息的节点进行操作,从而达到目的。然后我们将这些节点换成另一种数据结构,更改的时候对某些数据结构进行修改,就可以实现嵌套。二维树状数组其实是最好写的一种树套树。单点修改,区间查询就像上文说的一样,我们......
  • 树套树
    树套树树状数组,(动态开点线段树),平衡树二逼平衡树您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:查询\(k\)在区间内的排名查询区间内排名为\(k\)的值修改某一位值上的数值查询在区间内的前驱(前驱定义为小于,且最大的数)查询\(......
  • 树套树板子,但是带修莫队+值域分块
    \(\text{Link-LuoguBlog}\)原题传送门没啥重要的事情,就是终于过了这题非常开心,发现自己是莫队的时间戳部分写错了调了114514年我也只能说是十分趣味。以及今天深刻地认识到了带修莫队应该len=pow(n,0.66);。就是裸的带修莫队+值域分块,就不说了,直接放代码了昂。如果有人......
  • 树套树
    伪树套树CF19DPoints我们只关心最值而不是所有点的信息,所以不需要真的矩形查询对\(x\)建权值线段树,维护纵坐标最大值就能线段树二分求出询问矩形中最小的横坐标,再在这个横坐标上找最小纵坐标即可,可以在叶子上用set维护\(y\)实现。时间复杂度\(O(n\logn)\)......
  • 【树套树,LCT,出栈序】P4027 [NOI2007] 货币兑换
    其实是我Li-Chao-Tree哒!!考虑转移\(f_x=\minf_{anc}+(d_{x}-d_{anc})p_x+q_x\)其中\(anc\)为\(x\)的祖先,然后满足\(d_{anc}\geqd_{x}-li_{x})\)。考虑如果用权值线段树+带撤销的李超树可以维护\(li_{x}\)可以维护\(li_{x}<0\)的情况。但是这个题......
  • 【学习笔记】树套树
    所谓树套树,其本质是通过用树维护一组树的根,从而维护强悍的数据1线段树套平衡树线段树套#include<bits/stdc++.h>usingnamespacestd;#defineMAXN50005intseg[MAXN<<2];intamin=1000000,amax=0;structNode{ intval,rnd,siz; intch[2]; }t[MAXN*80];intt......
  • 【个人模板封装】树套树、高维数据结构
    前言这是我个人使用的一些模板封装,限于个人能力,可能存在诸多不足与漏洞,在未加测试直接使用前请务必小心谨慎。更新可能会滞后于我本地的文档,如有疑问或者催更之类的可以在评论区留言。全文模板测试均基于以下版本信息,请留意版本兼容问题。Windows,64bitG++(ISOC++20)stack......
  • 树套树——维护区间内权值信息的“重武器”
    Introduction树套树,顾名思义,就是将各类“树”据结构的节点换成“树”,以此解决一些问题。一般情况下,两层树分别维护区间信息和区间内权值的信息。而因为树套树极劣的空间复杂度和巨大的常数,经常需要使用动态开点和垃圾回收的方法降低空间复杂度,以及一定的卡常技巧(将较为短小......