平衡树

先挂个板子:

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
mt19937 sj(114514);
struct node{
    int l,r,key,sz,val,tag;
}tr[N];
int root,t1,t2,t3,ck;
int xj(int x){
    tr[++ck]={0,0,(int)sj(),1,x,0};
    return ck;
}
void pushup(int u){
    tr[u].sz=tr[tr[u].l].sz+tr[tr[u].r].sz+1;
}
void pushdown(int u){
    if(tr[u].tag){
        tr[u].val^=1;
        if(tr[u].l) tr[tr[u].l].tag^=1;
        if(tr[u].r) tr[tr[u].r].tag^=1;
        tr[u].tag=0;
    }
}
void split_rank(int u,int v,int &x,int &y){
    if(!u) return x=y=0,void();
    pushdown(u);
    int tmp=tr[tr[u].l].sz+1;
    if(tmp==v) x=u,y=tr[u].r,tr[u].r=0;
    else if(tmp>v) y=u,split_rank(tr[u].l,v,x,tr[u].l);
    else x=u,split_rank(tr[u].r,v-tmp,tr[u].r,y);
    pushup(u);
}
void split_val(int u,int v,int &x,int &y){
    if(!u) return x=y=0,void();
    if(tr[u].val>v) y=u,split_val(tr[u].l, v,x,tr[u].l);
    else x=u,split_val(tr[u].r,v,tr[u].r,y);
    pushup(u);
}
int merge(int x,int y){
    if(!x||!y) return x+y;
    pushdown(x),pushdown(y);
    if(tr[x].key>tr[y].key){
        tr[x].r=merge(tr[x].r,y);
        pushup(x);
        return x;
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}
void insert_rank(int x){
    split_rank(root,x-1,t1,t2);
    root=merge(merge(t1,xj(x)),t2);
}
void init(int n){
    root=t1=t2=t3=ck=0;
    for(int i=1;i<=n+100;i++) tr[i]={0};
}
void insert_val(int x){
    split_val(root,x,t1,t2);
    root=merge(merge(t1,xj(x)),t2);
}
void erase_val(int x){
    split_val(root,x,t1,t2);
    split_val(t1,x-1,t1,t3);
    t3=merge(tr[t3].l,tr[t3].r);
    root=merge(merge(t1,t3),t2);
}
void erase_rank(int x){
    split_rank(root,x-1,t1,t2);
    split_rank(t2,x,t2,t3);
    root=merge(t1,t3);
}
int find_kth(int k){
    int u=root;
    while(u){
        int tmp=tr[tr[u].l].sz+1;
        if(tmp==k) break;
        if(tmp>k) u=tr[u].l;
        else u=tr[u].r,k-=tmp;
    }
    return tr[u].val;
}   
void rev(int l,int r){
    split_rank(root,l-1,t1,t2);
    split_rank(t2,r-l+1,t2,t3);
    tr[t2].tag^=1;
    root=merge(merge(t1,t2),t3);
}
void dfs(int u){
    pushdown(u);
    if(tr[u].l) dfs(tr[u].l);
    cout<<tr[u].val<<' ';
    if(tr[u].r) dfs(tr[u].r);
}

 

 

由于我们学过数据结构,在二叉搜索树中,如果给出的数据是一条链的话,那么每次查询等操作都是O(n)的级别,所以为了优化搜索查找修改的效率,需要创建平衡树,来达到logn级别的查询和修改

对于一颗二叉搜索树,我们可以将它进行分裂操作,假如我们要进行查询一个数 $u$ 那么我们就可以分裂这棵树,把他分成两颗树,由于二叉树的性质,我们肯定能够把左边的树分成 $<= u$ 右边的树都是 $>u$ 的两颗树有了这两棵树,我们就可以进行查询,由于分裂之后二叉搜索树的性质不变,就可以做到查询操作了,不过由于我们分裂了,所以分裂之后不要忘记合并就是了

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
struct node{
    int l,r,key,sz,val;//左子树,右子树,关键值,树的大小,以及树中存的值
}tr[N];
int t1,t2,t3,ck,root;//t1是分裂之后左子树的根节点,t2是右子树的根节点
//t3是 x=v 的根节点,ck就是一个节点计数器,root是树的根
mt19937 sj(1141514); //随机数,我们将树的尽量分成大根堆的形式,key即为值
//由于正态分布的原因,所以我们创建出来的树可以压到log的高度
int xj(int x){
    tr[++ck]={0,0,(int)sj(),1,x};
    return ck;
}
void pushup(int u){
    tr[u].sz=tr[tr[u].l].sz+tr[tr[u].r].sz+1;
}
void split(int u,int v,int &x,int &y){
    if(!u) return x=y=0,void();
    if(tr[u].val>v) y=u,split(tr[u].l,v,x,tr[u].l);//若节点大于查询值,向左分裂
    else x=u,split(tr[u].r,v,tr[u].r,y);
    pushup(u);
}
int merge(int x,int y){
    if(!x||!y) return x+y;
    if(tr[x].key>tr[y].key){//大根堆性质合并 
        tr[x].r=merge(tr[x].r,y);
        pushup(x);
        return x;
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}
void insert(int x){
    split(root,x,t1,t2);
    root=merge(merge(t1,xj(x)),t2);
}
void erase(int x){
    split(root,x,t1,t2); //先根据x分成两半
    split(t1,x-1,t1,t3);//在从<=x的树上继续分成两半,此时分成 <=x-1, >x-1
    t3=merge(tr[t3].l,tr[t3].r);//t3即为 >x-1的根节点,所以我们直接合并左右子树即可
    //不加入根节点,因为此时根节点一定是 =x
    root=merge(merge(t1,t3),t2);//怎么分的树怎么合  
}
int find(int x){
    split(root,x-1,t1,t2);
    int res=tr[t1].sz+1;
    root=merge(t1,t2);
    return res;
}
int kth(int x){
    int u=root;
    while(u){
        int tmp=tr[tr[u].l].sz+1; //左子树的值全部比当前节点小
        if(tmp==x) break;
        else if(tmp>x) u=tr[u].l;
        else x-=tmp,u=tr[u].r;  
    }
    return tr[u].val;
}
int find_pre(int x){
    split(root,x-1,t1,t2);
    int u=t1;
    while(tr[u].r) u=tr[u].r;
    root=merge(t1,t2);
    return tr[u].val;
}
int find_next(int x){
    split(root,x,t1,t2);
    int u=t2;
    while(tr[u].l) u=tr[u].l;
    root=merge(t1,t2);
    return tr[u].val;
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n,m; cin>>n>>m;
    for(int i=1;i<=n;i++){
        int x; cin>>x;
        insert(x);
    }
    int lst=0,res=0;
    while(m--){
        int op,x; cin>>op>>x;
        x^=lst;
        if(op==1) insert(x);
        else if(op==2) erase(x);
        else if(op==3) lst=find(x),res^=lst;
        else if(op==4) lst=kth(x),res^=lst;
        else if(op==5) lst=find_pre(x),res^=lst;
        else lst=find_next(x),res^=lst;
    }
    cout<<res;
    return 0;
}

 

例题:

P4309 [TJOI2013] 最长上升子序列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

这是一个按排名分裂的平衡树,首先考虑如何按照排名进行分裂,对于一颗平衡树来说,如果我们要在位置 $i$ 插入一个数据,那么我们可以直接分裂到 $i-1$ 的位置,也就是我们的$t1$ 是前$i-1$位置的树,后面的则是其余的,所以我们可以直接合并即可,在分裂操作中,我们选取当前节点的左子节点的大小+1, 也就是当前节点的大小,那么当我们的要求的大小小于这个大小的时候,我们可以直接想递归左子树,直到有一颗满足我们分裂要求大小的树即可,如果大于这个大小,很明显我们要分裂右子树,那么在递归的时候我们要求的大小就不能是$i$了,我们要减去左子树的大小,可自行理解,这是插入操作

接着是查询操作,很明显我们不能直接暴力求出最长上升子序列,发现一个性质,由于是从1~n 开始插入的,所以新插入的值一定有以下性质:

1. 对于 1 ~ pos-1 我们的值肯定等于这些位置中以该位置结尾的最长上升子序列,我们设为 $dp$

2. 对于 pos ~ n 很明显我们的插入完全不影响后面的最长上升子序列的大小

所以我们的新插入节点的$val=dp[pos]+1$,在更新的函数中,我们要比较左右子树的$dp$值,以及自身的$val$

因为理解了很久,这里讲一下为什么要比较$val$而不是$dp[u]$,因为我们的价值是以$pos$结尾的最长上升子序列的长度

首先要明白节点中维护的 $val$ 是以当前节点为前缀的最大上升序列,$dp[u]$ 则是整体的,

考虑前面,如果前面的位置有新的值插入,很明显对我们的$pos$位置没有影响,因为我们的值肯定不如新插入的值大

考虑后面,有新的值插入,那么我们就要更新,而这个就是我们的左右子树里面的,所以我们更新的时候是我们前面的最大价值和左右子树的$dp$

不要忘记,我们在插入的时候需要进行分裂,而分裂之后,由于向上更新,每个$dp$都会改变,因为不是整个序列了,所以我们可用的就是当前节点之前的,分裂肯定不会把当前节点的父节点分裂掉,毕竟我们从x位置开始分裂,x位置之前的是一个整体,之后的是一个整体

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10,mod=1e9+7;
mt19937 sj(114514);
struct node{
    int l,r,key,sz,val;
}tr[N];
int root,t1,t2,t3,ck;
int dp[N];
int xj(int x){
    tr[++ck]={0,0,(int)sj(),1,x};
    dp[ck]=x;
    return ck;
}
void pushup(int u){
    tr[u].sz=tr[tr[u].l].sz+tr[tr[u].r].sz+1;
    dp[u]=max({dp[tr[u].l],dp[tr[u].r],tr[u].val});
}
void split(int u,int v,int &x,int &y){                    
    if(!u) return x=y=0,void();
    int k=tr[tr[u].l].sz+1;
    if(k==v) x=u,y=tr[u].r,tr[u].r=0;
    else if(k>v) y=u,split(tr[u].l,v,x,tr[u].l);
    else x=u,split(tr[u].r,v-k,tr[u].r,y);
    pushup(u);   
}
int merge(int x,int y){
    if(!x||!y) return x+y;
    // cout<<"x----y"<<endl<<x<<"---"<<y<<endl;
    // cout<<tr[x].key<<"---"<<tr[y].key<<endl;
    if(tr[x].key>tr[y].key){
        tr[x].r=merge(tr[x].r,y);
        // cout<<x<<"----x----"<<tr[x].r<<endl;
        pushup(x);
        return x;
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        // cout<<y<<"----y----"<<tr[y].l<<endl;
        pushup(y);
        return y;
    }
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n; cin>>n;
    for(int i=1;i<=n;i++){
        int x; cin>>x;
        split(root,x,t1,t2);
        t3=merge(xj(dp[t1]+1),t2);
        root=merge(t1,t3);
        // cout<<"t1--"<<t1<<" t2--"<<t2<<" t3--"<<t3<<"-----"<<root<<endl;
        cout<<dp[root]<<endl;
    }
    return 0;
}

 Problem - 702F - Codeforces

若直接模拟题意,时间复杂度为$O(n*m)$不可行

首先肯定是要对质量从大到小排序,对花费从大到小排序,设当前物品的花费为$v_i$,秉持着能买就买的原则,可以将所有的$b_i>=v_i$的贡献均加一,这个操作平衡树打个标记很容易做到

但是有一点需要注意,对于所有的$b_i<v_i$的人我们不做处理,对于$v_i+1,2*v_i$ 的区间,减去花费之后很明显就不保持单调性了,也就是破坏了平衡树的性质,那么此时我们需要暴力的修改这段区间即可

#include <bits/stdc++.h>
// #define int long long
using namespace std;
const int N=5e6+10,mod=1e9+7;
mt19937 sj(114514);
struct node{
    int l,r,key,sz,id,val,tag,cnt,lazy;
}tr[N];
int ck,root,t1,t2,t3;
int res[N];
int xj(int i,int x){
    tr[++ck]={0,0,(int)sj(),1,i,x,0,0,0};
    return ck;
}
void operate(int u,int v,int should){
    tr[u].val-=v,tr[u].cnt+=should;
    tr[u].tag+=v,tr[u].lazy+=should;
}
void pushup(int u){ 
    ;
}
void pushdown(int u){
    if(tr[u].tag){
        operate(tr[u].l,tr[u].tag,tr[u].lazy);
        operate(tr[u].r,tr[u].tag,tr[u].lazy);
        tr[u].tag=0,tr[u].lazy=0;
    }
}
void split_val(int u,int v,int &x,int &y){
    if(!u) return x=y=0,void();
    pushdown(u);
    if(tr[u].val>v) y=u,split_val(tr[u].l,v,x,tr[u].l);
    else x=u,split_val(tr[u].r,v,tr[u].r,y);
    pushup(u);
}
int merge(int x,int y){
    if(!x||!y) return x+y;
    pushdown(x),pushdown(y);
    if(tr[x].key>tr[y].key){
        tr[x].r=merge(tr[x].r,y);
        pushup(x);
        return x;
    }
    else{
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}
void insert(int i,int x){
    split_val(root,x,t1,t2);
    root=merge(merge(t1,xj(i,x)),t2);
}
void dfs(int u){
    pushdown(u);
    if(tr[u].l) dfs(tr[u].l);
    res[tr[u].id]=tr[u].cnt;
    if(tr[u].r) dfs(tr[u].r);
}
void make(int u){
    if(!u) return;
    pushdown(u);
    tr[++ck]=tr[u];
    tr[ck].l=tr[ck].r=0;
    split_val(root,tr[ck].val,t1,t2);
    root=merge(merge(t1,ck),t2);
    make(tr[u].l),make(tr[u].r);
}
signed main()
{
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    int n; cin>>n;
    vector<pair<int,int>>t(n+1);
    for(int i=1;i<=n;i++){
        int c,q; cin>>c>>q;
        t[i]={q,c};
    }
    auto cmp=[&](pair<int,int>a,pair<int,int>b)->bool{
        if(a.first==b.first)
            return a.second<b.second;
        else return a.first>b.first;
    };
    sort(t.begin()+1,t.end(),cmp);
    int m; cin>>m;
    for(int i=1;i<=m;i++){
        int b; cin>>b;
        insert(i,b);
    }
    for(int i=1;i<=n;i++){
        int v=t[i].second;
        split_val(root,v-1,t1,t2);
        operate(t2,v,1);
        split_val(t2,2*v-1,t2,t3);
        root=merge(t1,t3);
        make(t2);        
    }
    dfs(root);
    for(int i=1;i<=m;i++) cout<<res[i]<<' ';
    return 0;
}

 

posted @ 2024-04-21 18:04  o-Sakurajimamai-o  阅读(20)  评论(0编辑  收藏  举报
-- --