平衡树
先挂个板子:
#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; }
若直接模拟题意,时间复杂度为$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; }