Chanis
2018-08-03 08:52:13
-“平衡树好烦啊,转来转去的,而且treap遇到区间序列问题就gg。”
-“有一种不用旋转的treap,叫fhq treap,可以解决区间序列问题。”
我们做出以下约定:
int ch[MAXN][3];//0 左孩子,1右孩子
int val[MAXN];//每个点的权值
int rnd[MAXN];//每个点的随机权值
int size[MAXN];//以每个点为根的树的大小
分裂有两种,一种是权值分裂,一种是排名分裂。我们先讲权值分裂。如图,当我们遍历到一个节点时,如果它的权值小于k,那么它的左子树会被分到左边的树里,然后我们遍历它的右儿子,如果大于k,则把它的右子树分到右边的树里。如果到达递归边界now==0怎么办呢?这里会有两种情况:
1.root=0(即第一次split),很明显要给x和y初始化,即x=y=0。
2.split到了叶子节点,此时无法继续split了,只能返回。此时的x=y=0没什么用,因为x和y会在回溯的时候通过地址符号改变。
代码:
void split(int now,int k,int &x,int &y)
{
if(!now)
x=y=0;
else if(val[now]<=k)
{
x=now;
split(ch[now][1],k,ch[now][1],y);
}
else
{
y=now;
split(ch[now][0],k,x,ch[now][0]);
}
update(now);//update(i)为更新size[i]大小的函数
}
排名分裂与权值分裂类似,即将前k个放在一颗树里,其余的放在另一棵树里。代码:
void split(int now,int k,int &x,int &y)
{
if(!now)
x=y=0;
else
{
if(k<=siz[ch[now][0]])
{
y=now;
split(ch[now][0],k,x,ch[now][0]);
}
else
{
x=now;
split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y);
}
update(now);
}
}
如图,我们假设第一棵树的权值小于第二棵树的权值,那么我们就比较它们的随机权值。如果rnd[l]<rnd[r],我们就保留它的左子树,另一棵树作为它的右子树;如果rnd[l]>=rnd[r],那我们可以保留第二棵树的右子树,另一颗树作为它的左子树。代码:
int merge(int A,int B)
{
if(!A||!B)
return A+B;
if(rnd[A]<rnd[B])
{
ch[A][1]=merge(ch[A][1],B);
update(A);
return A;
}
else
{
ch[B][0]=merge(A,ch[B][0]);
update(B);
return B;
}
}
以上操作完成后,我们就可以用函数式编程的思想完成剩余的操作。
直接暴力merge,代码:
int new_node(int a)//新建一个节点
{
size[++cnt]=1;
val[cnt]=a;
rnd[cnt]=rand();
return cnt;
}
void insert(int a)//插入
{
spilt(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
删除权值为v的点,先把整颗树以v为权值split成两棵树a,b,再把a树按照v-1分成c,d。这时候值为v的点一定为d的根,那么我们把d的两个子儿子merge起来(划重点:这一步就是去除掉v的影响),再把他们重新merge起来得到一个新的树,这颗树就去除掉了v的影响。代码:
void del(int a)
{
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
直接按照a-1的权值把树分开,那么x树中最大的应该小于等于a-1,那么a的排名就是size[x]+1。代码:
int myrank(int a)
{
split(root,a-1,x,y);
int res=size[x]-1;
root=merge(x,y);
return res;
}
不想说什么,直接看代码:
int findKth(int k)
{
return val[myrank(root,k)];
}
我们先看前驱,因为要小于a,所以我们还是按照a-1的权值划分x,现在x中最大的数一定小于等于a-1,所以我们直接输出x中最大的数就好,后继同理,不再赘述。代码:
int pre(int a)
{
split(root,a-1,x,y);
int res=val[findKth(x,siz[x])];
root=merge(x,y);
return res;
}
int nxt(int a)
{
split(root,a,x,y);
int res=val[findKth(y,1)];
root=merge(x,y);
return res;
}
查询一个区间[l, r]就把一棵树split成三棵树,查中间那棵,再把它们merge回去。代码:
void add(int l,int r,int delta)//任意操作
{
int x,y,z;
split(root,x,y,r);
split(x,z,x,l-1);
addone(x,delta);//任意操作
merge(x,z,x);
merge(root,x,y);
}
fhq treap可以可持久化。如图,每次split和merge走到的所有点都新建一个即可。注意下传标记也要新建点。
1.洛谷P3369 普通disco平衡树
就是把上面的东西综合在一起。代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<ctime>
#include<cstdlib>
#define maxn 500001
using namespace std;
int size[maxn],ch[maxn][2],fix[maxn],val[maxn];
int T,cnt,n,m,x,y,z,p,a,root,com;
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
inline void update(int x)
{
size[x]=1+size[ch[x][0]]+size[ch[x][1]];
}
inline int new_node(int x)
{
size[++cnt]=1;
val[cnt]=x;
fix[cnt]=rand();
return cnt;
}
int merge(int A,int B)
{
if(!A||!B) return A+B;
if(fix[A]<fix[B]){ch[A][1]=merge(ch[A][1],B); update(A); return A;}
else {ch[B][0]=merge(A,ch[B][0]); update(B); return B;}
}
void split(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else
{
if(val[now]<=k) x=now,split(ch[now][1],k,ch[now][1],y);
else y=now,split(ch[now][0],k,x,ch[now][0]);
update(now);
}
}
int kth(int now,int k)
{
while(1)
{
if(k<=size[ch[now][0]]) now=ch[now][0];
else if(k==size[ch[now][0]]+1) return now;
else k-=size[ch[now][0]]+1,now=ch[now][1];
}
}
int main()
{
srand((unsigned)time(NULL));
T=read();
while(T--)
{
p=read();a=read();
if(p==1)
{
split(root,a,x,y);
root=merge(merge(x,new_node(a)),y);
}
else if(p==2)
{
split(root,a,x,z);
split(x,a-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);
}
else if(p==3)
{
split(root,a-1,x,y);
printf("%d\n",size[x]+1);
root=merge(x,y);
}
else if(p==4) printf("%d\n",val[kth(root,a)]);
else if(p==5)
{
split(root,a-1,x,y);
printf("%d\n",val[kth(x,size[x])]);
root=merge(x,y);
}
else
{
split(root,a,x,y);
printf("%d\n",val[kth(y,1)]);
root=merge(x,y);
}
}
return 0;
}
本题还有一种树状数组解法:树状数组解法。
2.洛谷P3391 文艺平衡树。
区间操作。代码(感谢守望的代码):
# include<iostream>
# include<cstdio>
# include<cstring>
# include<cstdlib>
using namespace std;
const int MAX=1e5+1;
int n,m,tot,rt;
struct Treap{
int pos[MAX],siz[MAX],w[MAX];
int son[MAX][2];
bool fl[MAX];
void pus(int x)
{
siz[x]=siz[son[x][0]]+siz[son[x][1]]+1;
}
int build(int x)
{
w[++tot]=x,siz[tot]=1,pos[tot]=rand();
return tot;
}
void down(int x)
{
swap(son[x][0],son[x][1]);
if(son[x][0]) fl[son[x][0]]^=1;
if(son[x][1]) fl[son[x][1]]^=1;
fl[x]=0;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(pos[x]<pos[y])
{
if(fl[x]) down(x);
son[x][1]=merge(son[x][1],y);
pus(x);
return x;
}
if(fl[y]) down(y);
son[y][0]=merge(x,son[y][0]);
pus(y);
return y;
}
void split(int i,int k,int &x,int &y)
{
if(!i)
{
x=y=0;
return;
}
if(fl[i]) down(i);
if(siz[son[i][0]]<k)
x=i,split(son[i][1],k-siz[son[i][0]]-1,son[i][1],y);
else
y=i,split(son[i][0],k,x,son[i][0]);
pus(i);
}
void coutt(int i)
{
if(!i) return;
if(fl[i]) down(i);
coutt(son[i][0]);
printf("%d ",w[i]);
coutt(son[i][1]);
}
}Tree;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
rt=Tree.merge(rt,Tree.build(i));
for(int i=1;i<=m;i++)
{
int l,r,a,b,c;
scanf("%d%d",&l,&r);
Tree.split(rt,l-1,a,b);
Tree.split(b,r-l+1,b,c);
Tree.fl[b]^=1;
rt=Tree.merge(a,Tree.merge(b,c));
}
Tree.coutt(rt);
return 0;
}
3.可持久化序列
可持久化序列这道题要求支持三个操作:
1 l r,翻转l到r的区间;
2 l r,询问l的到r的区间和;
3 p,回到p时刻。
每次修改新建点打翻转标记即可,代码(感谢permui的代码):
#include<cstdio>
#include<cctype>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<algorithm>
using namespace std;
typedef pair<int,int> Pair;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int maxn=5e4+5;
const int nlogn=1.3e7+5;
struct node {
int x,hp,l,r,sum,size;
bool rev;
void clear() {
x=hp=l=r=sum=size=rev=0;
}
};
struct TREAP {
int pool[nlogn];
int pooler;
node t[nlogn];
int now,all;
int root[maxn];
TREAP ():now(0),pooler(1) {
for (int i=1;i<nlogn;++i) pool[i]=i;
root[now]=pool[pooler++];
}
int newroot() {
int ret=pool[pooler++];
return ret;
}
int newnode(int x) {
int ret=pool[pooler++];
t[ret].hp=rand();
t[ret].size=1;
t[ret].x=t[ret].sum=x;
return ret;
}
void delnode(int x) {
t[x].clear();
pool[--pooler]=x;
}
void next() {
root[++all]=newroot();
t[root[all]]=t[root[now]];
now=all;
}
void back(int x) {
now=x;
}
void update(int x) {
t[x].sum=t[x].x+t[t[x].l].sum+t[t[x].r].sum;
t[x].size=t[t[x].l].size+t[t[x].r].size+1;
}
void pushdown(int x) {
if (!t[x].rev) return;
if (t[x].l) {
int tx=newnode(t[t[x].l].x);
t[tx]=t[t[x].l];
t[tx].rev^=true;
t[x].l=tx;
}
if (t[x].r) {
int tx=newnode(t[t[x].r].x);
t[tx]=t[t[x].r];
t[tx].rev^=true;
t[x].r=tx;
}
swap(t[x].l,t[x].r);
t[x].rev=false;
}
int merge(int x,int y) {
if (!x) return y;
if (!y) return x;
int now;
if (t[x].hp<=t[y].hp) {
now=newnode(t[x].x);
t[now]=t[x];
pushdown(now);
t[now].r=merge(t[now].r,y);
} else {
now=newnode(t[y].x);
t[now]=t[y];
pushdown(now);
t[now].l=merge(x,t[now].l);
}
update(now);
return now;
}
Pair split(int x,int p) {
if (t[x].size==p) return make_pair(x,0);
int now=newnode(t[x].x);
t[now]=t[x];
pushdown(now);
int l=t[now].l,r=t[now].r;
if (t[l].size>=p) {
t[now].l=0;
update(now);
Pair g=split(l,p);
now=merge(g.second,now);
return make_pair(g.first,now);
} else if (t[l].size+1==p) {
t[now].r=0;
update(now);
return make_pair(now,r);
} else {
t[now].r=0;
update(now);
Pair g=split(r,p-t[l].size-1);
now=merge(now,g.first);
pushdown(now);
return make_pair(now,g.second);
}
}
void rever(int l,int r) {
++l,++r;
Pair g=split(root[now],l-1);
Pair h=split(g.second,r-l+1);
int want=h.first;
int here=newnode(t[want].x);
t[here]=t[want];
t[here].rev^=true;
int fi=merge(g.first,here);
int se=merge(fi,h.second);
root[now]=se;
}
int query(int l,int r) {
++l,++r;
Pair g=split(root[now],l-1);
Pair h=split(g.second,r-l+1);
int want=h.first;
int ret=t[want].sum;
int fi=merge(g.first,want);
int se=merge(fi,h.second);
root[now]=se;
return ret;
}
void insert(int x) {
int k=newnode(x);
root[now]=merge(root[now],k);
}
} Treap;
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
freopen("my.out","w",stdout);
#endif
srand(time(0));
int n=read(),m=read();
for (int i=1;i<=n;++i) {
int x=read();
Treap.insert(x);
}
while (m--) {
int op=read();
if (op==1) {
Treap.next();
int l=read(),r=read();
Treap.rever(l,r);
} else if (op==2) {
int l=read(),r=read();
int ans=Treap.query(l,r);
printf("%d\n",ans);
} else if (op==3) {
Treap.back(read());
}
}
return 0;
}
1.洛谷P2596 书架
2.洛谷P4309 最长上升子序列
3.洛谷P3987 我永远喜欢珂朵莉~
4.洛谷P3920 紫荆花之恋(在做此题前,你需要掌握点分治,你还需要一点替罪羊树的思想,不难,就是当一个点过于不平衡时,我们就拍掉重建)。