树套树初探

最近学了学树套树,做了几道模板题。

发现好像有点水

咳咳咳。

树套树,顾名思义,一个树套一个树。比如树状数组套平衡树,就是把树状数组的每一个结点作为一颗平衡树,线段树套权值线段树,就是一颗线段树,每一个结点都是一颗权值线段树。。。

如果有一个问题是要求一个区间\([l,r]\)中比\(x\)小的数有多少个带单点修改\(n<=50000\),可以用树状数组套平衡树解决,首先在每个点上建立平衡树,然后再拿树状数组维护起来,然后我们可以先求出区间\([1,r]\)中有多少个比\(x\)小的数减去区间\([1,l-1]\)中有多少个比\(x\)小的数,每一个\([1,x]\)的区间我们用树状数组选出\(log\)颗平衡树,在每颗平衡树上找出比\(x\)小的最后加起来就行了。修改就在树状数组上对应的平衡树中删除插入就行了。这样每次询问的复杂度为\(log^2n\)。

下面看几道题:

P3380 【模板】二逼平衡树(树套树)

嗯,这题有很多做法。

首先可以用树状数组套平衡树做。

排名本质上就是有多少比\(x\)小的。

第k小二分答案然后查排名加一个\(log\)是\(log^3n\)的。

前趋后继就是求出树状数组每一个点的前驱后继然后取\(\text{max}\)或\(\text{min}\)就行。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<ctime>
#include<cstdlib>
using namespace std;
const int INF=1e8;
const int N=5e4+100;
int tot,rad[N*30],size[N*30],v[N*30],ch[N*30][2],root[N<<3],x,y,z,n,m,a[N];
struct tree{
    int l,r;
}tr[N<<3];
int new_node(int x){
    int now=++tot;
    rad[now]=rand();size[now]=1;v[now]=x;
    return now;
}
void update(int now){
    size[now]=size[ch[now][0]]+size[ch[now][1]]+1;
}
int merge(int x,int y){
    if(x==0||y==0)return x+y;
    if(rad[x]>rad[y]){
        ch[x][1]=merge(ch[x][1],y);
        update(x);
        return x;
    }
    else{
        ch[y][0]=merge(x,ch[y][0]);
        update(y);
        return y;
    }
}
void split(int &x,int &y,int now,int k){
    if(now==0)x=y=0;
    else{
        if(v[now]<=k){
            x=now;
            split(ch[x][1],y,ch[x][1],k);
        }
        else {
            y=now;
            split(x,ch[y][0],ch[y][0],k);
        }
        update(now);
    }
}
void ins(int now,int w){
    split(x,y,root[now],w);
    root[now]=merge(merge(x,new_node(w)),y);
}
void ins(int x,int w,int now){
    ins(now,w);
    if(tr[now].l==tr[now].r)return;
    int mid=(tr[now].l+tr[now].r)>>1;
    if(x>mid)ins(x,w,now*2+1);
    else ins(x,w,now*2);
}
int rank(int now,int k){
    split(x,y,root[now],k-1);
    int w=size[x];
    root[now]=merge(x,y);
    return w;
}
int rank(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r){
        return rank(now,k);
    }
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return rank(l,r,k,now*2+1);
    else if(r<=mid)return rank(l,r,k,now*2);
    else return rank(l,mid,k,now*2)+rank(mid+1,r,k,now*2+1);
}
void work(int l,int r,int k){
    int L=0,R=INF,ans;
    while(L<=R){
        int mid=(L+R)>>1;
        if(rank(l,r,mid,1)<k){
            ans=mid;
            L=mid+1;
        }
        else R=mid-1;
    }
    printf("%d\n",ans);
}
void del(int now,int w){
    split(x,z,root[now],w);
    split(x,y,x,w-1);
    y=merge(ch[y][0],ch[y][1]);
    root[now]=merge(merge(x,y),z);
}
void del(int x,int w,int now){
    del(now,w);
    if(tr[now].l==tr[now].r)return;
    int mid=(tr[now].l+tr[now].r)>>1;
    if(x>mid)del(x,w,now*2+1);
    else del(x,w,now*2);
}
int kth(int now,int k){
    int l=ch[now][0];
    if(size[l]>=k)return kth(l,k);
    else if(size[l]+1==k)return v[now];
    else return kth(ch[now][1],k-size[l]-1);
}
int pre(int now,int k){
    int ans;
    split(x,y,root[now],k-1);
    if(size[x]==0)ans=-2147483647;
    else ans=kth(x,size[x]);
    root[now]=merge(x,y);
    return ans;
}
int pre(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r){
        return pre(now,k);
    }
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return pre(l,r,k,now*2+1);
    else if(r<=mid)return pre(l,r,k,now*2);
    else return max(pre(l,mid,k,now*2),pre(mid+1,r,k,now*2+1));
}
int suc(int now,int k){
    int ans;
    split(x,y,root[now],k);
    if(size[y]==0)ans=2147483647;
    else ans=kth(y,1);
    root[now]=merge(x,y);
    return ans;
}
int suc(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r){
        return suc(now,k);
    }
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return suc(l,r,k,now*2+1);
    else if(r<=mid)return suc(l,r,k,now*2);
    else return min(suc(l,mid,k,now*2),suc(mid+1,r,k,now*2+1));
}
void build(int l,int r,int now){
    tr[now].l=l;tr[now].r=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(l,mid,now*2);
    build(mid+1,r,now*2+1);
}
int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){sum=sum*10+ch-‘0‘;ch=getchar();}
    return sum*f;
}
int main(){
    srand(time(NULL));
    n=read();m=read();
    build(1,n,1);
    for(int i=1;i<=n;++i)ins(i,a[i]=read(),1);
    while(m--){
        int type=read(),l=read(),r=read();
        if(type==1){
            int k=read();
            printf("%d\n",rank(l,r,k,1)+1);
        }
        else if(type==2){
            int k=read();
            work(l,r,k);
        }
        else if(type==3){
            del(l,a[l],1);
            ins(l,a[l]=r,1);
        }
        else if(type==4){
            int k=read();
            printf("%d\n",pre(l,r,k,1));
        }
        else{
            int k=read();
            printf("%d\n",suc(l,r,k,1));
        }
    }
    return 0;
} 

然后我还写了一个线段树套权值线段树。

这个对于求第k大可以换一种求法。

就是先找到区间对应的线段树的区间编号(其实就是对应的权值线段树的根的编号),拿一个数组存下来,然后在权值线段树上二分就行了。这样复杂度就变成了\(log^2n\)的了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=5e4+100;
int sum[15000000],ch[15000000][2],root[N<<6];
int cnt,tot,n,m,num,c[N],b[N<<6],a[N],l[N],r[N],x[N],type[N];
struct tree{
    int l,r;
}tr[N<<6];
void build(int l,int r,int now){
    tr[now].l=l;tr[now].r=r;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(l,mid,now*2);
    build(mid+1,r,now*2+1);
}
void add(int l,int r,int w,int k,int &now){
    if(now==0){
        now=++cnt;
        ch[now][0]=ch[now][1]=0;
        sum[now]=0;
    }
    sum[now]+=k;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(w>mid)add(mid+1,r,w,k,ch[now][1]);
    else add(l,mid,w,k,ch[now][0]);
}
void add(int x,int w,int k,int now){
    add(1,tot,w,k,root[now]);
    if(tr[now].l==tr[now].r)return;
    int mid=(tr[now].l+tr[now].r)>>1;
    if(x>mid)add(x,w,k,now*2+1);
    else add(x,w,k,now*2);
}
int check(int l,int r,int L,int R,int &now){
    if(L>R)return 0;
    if(now==0){
        now=++cnt;
        ch[now][0]=ch[now][1]=0;
        sum[now]=0;
    }
    if(sum[now]==0)return 0;
    if(l==L&&r==R)return sum[now];
    int mid=(l+r)>>1;
    if(L>mid)return check(mid+1,r,L,R,ch[now][1]);
    else if(R<=mid)return check(l,mid,L,R,ch[now][0]);
    else return check(l,mid,L,mid,ch[now][0])+check(mid+1,r,mid+1,R,ch[now][1]);
}
int rank(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r)return check(1,tot,1,k-1,root[now]);
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return rank(l,r,k,now*2+1);
    else if(r<=mid)return rank(l,r,k,now*2);
    else return rank(l,mid,k,now*2)+rank(mid+1,r,k,now*2+1);
}
void find(int l,int r,int now){
    if(tr[now].l==l&&tr[now].r==r){
        c[++num]=root[now];
        return;
    }
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)find(l,r,now*2+1);
    else if(r<=mid)find(l,r,now*2);
    else find(l,mid,now*2),find(mid+1,r,now*2+1);
}
int kth(int l,int r,int k){
    num=0;
    find(l,r,1);
    int L=1,R=tot;
    while(L!=R){
        int tmp=0,mid=(L+R)>>1;
        for(int i=1;i<=num;i++)tmp+=sum[ch[c[i]][0]];
        if(tmp>=k){
            R=mid;
            for(int i=1;i<=num;i++)c[i]=ch[c[i]][0];
        }
        else {
            L=mid+1;k-=tmp;
            for(int i=1;i<=num;i++)c[i]=ch[c[i]][1];
        }
    }
    return L;
}
int kth(int l,int r,int k,int now){
    if(l==r)return l;
    int mid=(l+r)>>1;
    if(sum[ch[now][0]]>=k)return kth(l,mid,k,ch[now][0]);
    else return kth(mid+1,r,k-sum[ch[now][0]],ch[now][1]);
}
int pre(int now,int k){
    if(now==0)return 0;
    int tmp=check(1,tot,1,k-1,now);
    if(tmp==0)return 0;
    else return kth(1,tot,tmp,now);
}
int pre(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r)return pre(root[now],k);
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return pre(l,r,k,now*2+1);
    else if(r<=mid)return pre(l,r,k,now*2);
    else return max(pre(l,mid,k,now*2),pre(mid+1,r,k,now*2+1));
}
int suc(int now,int k){
    if(now==0)return tot+1;
    int tmp=check(1,tot,k+1,tot,now);
    if(tmp==0)return tot+1;
    else return kth(1,tot,check(1,tot,1,k,now)+1,now);
}
int suc(int l,int r,int k,int now){
    if(tr[now].l==l&&tr[now].r==r)return suc(root[now],k);
    int mid=(tr[now].l+tr[now].r)>>1;
    if(l>mid)return suc(l,r,k,now*2+1);
    else if(r<=mid)return suc(l,r,k,now*2);
    else return min(suc(l,mid,k,now*2),suc(mid+1,r,k,now*2+1));
}
int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();}
    while(ch>=‘0‘&&ch<=‘9‘){sum=sum*10+ch-‘0‘;ch=getchar();}
    return sum*f;
}
int main(){
    n=read();m=read();
    build(1,n,1);
    for(int i=1;i<=n;i++)a[i]=read(),b[++tot]=a[i];
    for(int i=1;i<=m;i++){
        type[i]=read(),l[i]=read(),r[i]=read();
        if(type[i]==3)b[++tot]=r[i];
        else{
            x[i]=read();
            if(type[i]!=2)b[++tot]=x[i];
        }
    }
    sort(b+1,b+1+tot);
    tot=unique(b+1,b+1+tot)-b-1;
    b[0]=-2147483647;b[tot+1]=2147483647;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(b+1,b+1+tot,a[i])-b;
        add(i,a[i],1,1);
    }
    for(int i=1;i<=m;i++)
        if(type[i]==3)r[i]=lower_bound(b+1,b+1+tot,r[i])-b;
        else if(type[i]!=2)x[i]=lower_bound(b+1,b+1+tot,x[i])-b;
    for(int i=1;i<=m;i++){
        if(type[i]==1)printf("%d\n",rank(l[i],r[i],x[i],1)+1);
        else if(type[i]==2)printf("%d\n",b[kth(l[i],r[i],x[i])]);
        else if(type[i]==3)add(l[i],a[l[i]],-1,1),add(l[i],a[l[i]]=r[i],1,1);
        else if(type[i]==4)printf("%d\n",b[pre(l[i],r[i],x[i],1)]);
        else if(type[i]==5)printf("%d\n",b[suc(l[i],r[i],x[i],1)]);
    }
    return 0;
}

还有一种树状数组套权值线段树的方法,利用了k小的可减性。优化了常数和代码量。

但是我没写。。。

原文地址:https://www.cnblogs.com/Xu-daxia/p/10099540.html

时间: 2024-10-17 23:24:46

树套树初探的相关文章

BZOJ_3196_二逼平衡树(树套树:线段树+Treap)

描述 可以处理区间问题的平衡树. 分析 树套树.可以用线段树套Treap.人生第一道树套树的题... op1:如果在整区间,直接在该区间的treap上求解.否则分两个区间求解,然后相加.最后+1. op2:这个不太好直接做,可以二分,每次假定一个值,用这个值去做op1,以此求得一个rank=k+1的数,求rank=k的数等价与求这个数的前驱pre. op3:先删后加. op4&op5:如果在整区间,直接在该区间的treap上求解,否则分量个区间求解,pre取最大值,suc取最小值.注意有些数在有

BZOJ 3110: [Zjoi2013]K大数查询 [树套树]

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6050  Solved: 2007[Submit][Status][Discuss] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a

[BZOJ3110] [Zjoi2013] K大数查询 (树套树)

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M 接下来M行,每行形如1 a b c或2 a b c Output 输出每个询问的结果 Sample Input 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 Sample Output 1 2 1 HINT [

bzoj 3295: [Cqoi2011]动态逆序对(树套树 or CDQ分治)

Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. Output 输出包含m行,依次为删除每个元素之前,逆序对的个数. Sample Input 5 4 1 5 3

BZOJ3295 动态逆序对 树套树, 树状数组套线段树(主席树)

Orz黄学长,蒟蒻在黄学长的带领下,通过阅读黄学长的代码!终于会了这道题! 首先我想先说一下这道题的思路(准确来说是黄学长的). 很明显,树状数组应该不用讲吧!关键是内存怎么开,维护一些什么样的数据? 其实我们通过观察,很快可以发现,你维护被删的数比维护所有的数轻松多了(不管是空间上,还是时间上).所以我们就可以从这方面想!(其实我一开始的思路,因为这道题我已经看过很久了,一直想写,毕竟是白书里面的一道例题嘛!一开始,蒟蒻的我是打算这样的用树状数组套权值线段树,并且是维护所有的数,我发现空间不够

HDU HDOJ5412(树套树

题目:要求支持带修改维护区间第k大的值.所谓的动态区间第k大. 思路:题解说的是树状数组套treap,然而没想通树状数组怎么维护...线段树的话就是把所有的值离散化一下,离线建个关于值的线段树,每个节点是一个treap,treap里的值用位置做关键字,然后做区间查询,复杂度是O(nlogn*logn).基本也是经典的树套树做法....然后赛后写了两遍都没过.....今天心血来潮再挑战一下,结果从8点调到晚上1点.其间各种爆内存各种re各种t.....随机数据对拍了好久没拍出问题来....然后一直

bzoj 1901: Zju2112 Dynamic Rankings(树套树)

1901: Zju2112 Dynamic Rankings 经典的带修改求区间第k小值问题 树套树模板,我是用的线段树套splay实现的,而且用的数组模拟的,所以可能空间略大,bzoj过了,zoj过不了. 思路很简单,用线段树维护区间,用splay维护区间内的权值,然后询问的时候,二分答案key,然后在区间内找小于key的数有多少个. 贴上模板: #include<stdio.h> #include<string.h> #include<algorithm> #def

POJ 1195 2维线段树(树套树实现) 树状数组

1: #include <stdio.h> 2: #include <string.h> 3: #include <stdlib.h> 4: #include <algorithm> 5: #include <iostream> 6: using namespace std; 7:   8: #define LL(a) a<<1 9: #define RR(a) a<<1|1 10: const int MaxL = 10

[树套树]K大数查询

有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少.为了强制在线,每一次的a,b是加密的,需要异或lastans的后8位进行解密,其中lastans为上次输出的结果,初始为零.如果解密后a>b则先交换a,b数据保证解密后a,b不会超过N如果解密后a,b出现0,则赋值为1. 来历:bzoj上的一道题,经过子祯学长的魔改后,数据范围变得很奇怪... 算法:树