伸展树复习
Posted 日拱一卒 功不唐捐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了伸展树复习相关的知识,希望对你有一定的参考价值。
T1 郁闷的出纳员
一个数据结构,支持单点插入、删除几个不一定连续的点、查询k值操作
初做:2017.2.18 time:1268ms memory:3MB
http://www.cnblogs.com/TheRoadToTheGold/p/6412790.html
现在:2017.3.28 time:570ms memory:3MB
初做时直接套模板,删除分5种情况,
本题实际只需3种情况
这一次的查询用递归写的
int query(int now,int k) { int tmp=0; if(ch[now][0]) tmp=sum[ch[now][0]]; if(k<=tmp) return query(ch[now][0],k); if(tmp+cnt[now]>=k) return key[now]; return query(ch[now][1],k-tmp-cnt[now]); }
5行开始写的时候每一行都有错误
第二行:tmp=左子树节点个数,遗漏了判断是否有左子树
第三行:当k<=左子树节点个数,漏了等于号
第四行:左子树节点个数+根节点个数(cnt[]数组)>=k,cnt[]和sum[]混了,>写的<
第五行:cnt和sum混了
再就是splay写丑了
贴个精简的:
inline void splay(int x) { for(int fa;fa=f[x];rotate(x)) if(f[fa]) rotate(getson(x)==getson(fa) ? fa:x); root=x; update(x); }
一定要注意update时节点是否有左右孩子
rotate里出现了一个错误:
if(z) ch[z][ch[z][1]==y]=x;
fa[x]=z;这一句是不在if(z)里的
#include<cstdio> #define N 100001 using namespace std; int n,limit,gather,root,tot,leave; int sum[N],ch[N][2],fa[N],key[N],cnt[N]; void newroot(int x) { sum[++tot]=cnt[tot]=1; key[tot]=x; root=tot; } void newnode(int f,int x) { sum[++tot]=cnt[tot]=1; key[tot]=x; fa[tot]=f; ch[f][x>key[f]]=tot; } void update(int x) { sum[x]=cnt[x]; if(ch[x][0]) sum[x]+=sum[ch[x][0]]; if(ch[x][1]) sum[x]+=sum[ch[x][1]]; } void rotate(int x,int kind) { int y=fa[x],z=fa[y]; ch[y][!kind]=ch[x][kind]; fa[ch[y][!kind]]=y; ch[x][kind]=y;fa[y]=x; fa[x]=z; if(z) ch[z][ch[z][1]==y]=x; update(y); } void splay(int x) { while(fa[x]) { int y=fa[x],kind=ch[y][1]==x; if(!fa[y]) rotate(x,!kind); else { int z=ch[fa[y]][1]==y; if(z==kind) { rotate(y,!kind); rotate(x,!kind); } else { rotate(x,!kind); rotate(x,kind); } } } update(x); root=x; } void insert(int x) { if(!root) { newroot(x); return; } int now=root; while(ch[now][x>key[now]]) { if(key[now]==x) { cnt[now]++; update(now); splay(now); return; } now=ch[now][x>key[now]]; } if(key[now]==x) { cnt[now]++; update(now); splay(now); return; } newnode(now,x); update(now); splay(tot); } void cut_lch() { int tmp=ch[root][0]; if(tmp) leave+=sum[tmp]; fa[tmp]=0; ch[root][0]=0; update(root); } void cut() { if(cnt[root]>1) { cnt[root]--; update(root); return; } if(!ch[root][1]) { root=0; gather=0; return; } int tmp=ch[root][1]; fa[tmp]=0; ch[root][1]=0; root=tmp; } int query(int now,int k) { int tmp=0; if(ch[now][0]) tmp=sum[ch[now][0]]; if(k<=tmp) return query(ch[now][0],k); if(tmp+cnt[now]>=k) return key[now]; return query(ch[now][1],k-tmp-cnt[now]); } int main() { freopen("cashier.in","r",stdin); freopen("cashier.out","w",stdout); scanf("%d%d",&n,&limit); char ch[1];int x; while(n--) { scanf("%s%d",ch,&x); switch(ch[0]) { case \'I\': if(x<limit) continue; insert(x-gather); break; case \'A\': gather+=x; break; case \'S\': gather-=x; insert(limit-gather); cut_lch(); cut(); break; case \'F\': if(x>sum[root]) printf("-1\\n"); else printf("%d\\n",query(root,sum[root]-x+1)+gather); } } printf("%d",leave); }
T2 反转卡片
http://www.cnblogs.com/TheRoadToTheGold/p/6414979.html
初做:2017.2.19 现在:2017.3.28
3个小时
这个题使splay中的节点编号与节点在序列中的顺序保持一致,然后splay的中序遍历就是卡片顺序。
所以第i张卡片相当于splay中排名为i的节点,而且不需要在splay中存储权值,即卡片上的数字
所以以前认为将卡片编号作为权值建splay是错误的
#include<cstdio> #include<algorithm> #define N 300010 using namespace std; int n,num[N],root; int fa[N],ch[N][2],sum[N]; bool tag[N]; void update(int x) { sum[x]=1+sum[ch[x][0]]+sum[ch[x][1]]; } void build(int l,int r,int f) { if(l>r) return; int mid=l+r>>1; fa[mid]=f;sum[mid]=1; ch[f][mid>f]=mid; build(l,mid-1,mid); build(mid+1,r,mid); update(mid); } int getson(int x) { return ch[fa[x]][1]==x; } void rotate(int x,int & goal) { int y=fa[x],z=fa[y],kind=getson(x); if(y==goal) goal=x; else ch[z][ch[z][1]==y]=x; ch[y][kind]=ch[x][!kind]; fa[ch[y][kind]]=y; ch[x][!kind]=y; fa[y]=x; fa[x]=z; update(y); } void down(int x) { tag[x]^=1; if(ch[x][0]) tag[ch[x][0]]^=1; if(ch[x][1]) tag[ch[x][1]]^=1; swap(ch[x][0],ch[x][1]); } void splay(int x,int & goal) { while(x!=goal) { if(tag[fa[fa[x]]]) down(fa[fa[x]]); if(tag[fa[x]]) down(fa[x]); if(tag[x]) down(x); int y=fa[x]; if(y!=goal) { if(getson(y)==getson(x)) rotate(y,goal); else rotate(x,goal); } rotate(x,goal); } update(x); } int find(int now,int x) { if(tag[now]) down(now); int tmp=0; if(ch[now][0]) tmp=sum[ch[now][0]]; if(x<=tmp) return find(ch[now][0],x); if(tmp+1>=x) return now; return find(ch[now][1],x-tmp-1); } void rever(int l,int r) { int x=find(root,l-1),y=find(root,r+1); splay(x,root);splay(y,ch[x][1]); tag[ch[y][0]]^=1; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&num[i+1]); build(1,n+2,0); root=(n+3)/2; int maxn=100000,id,r,ans=0; while(maxn--) { id=find(root,2); r=num[id]; if(r==1) { printf("%d",100000-maxn-1); return 0; } rever(2,r+1); } printf("-1"); }
列举代码中好几个错误:
①、答案的输出
if(r==1) { printf("%d",100000-maxn-1); return 0; }
而不是 100000-maxn
因为开头写的 while(maxn--)
判断完maxn为true后,减1,输出里的maxn相对于while里已经减了1,所以应该是100000-(maxn+1)
②、rever函数中,打翻转标记应该是tag[ch[y][0]]^=1;
而不是tag[ch[y][0]]=1
③、find函数里now和x混了,属于不过脑子的错误
④、splay里用了这个
inline void splay(int x) { for(int fa;fa=f[x];rotate(x)) if(f[fa]) rotate(getson(x)==getson(fa) ? fa:x); root=x; update(x); }
上面的判断标准是f[fa]为true,因为根节点的父节点是0
这个题是将点转到指定位置,所以判断标准是父节点是否是目标位置
修改上面的代码改了好久就是不过
所以,若是将点转到指定位置,干脆用这个
void splay(int x,int & goal) { while(x!=goal) { if(tag[fa[fa[x]]]) down(fa[fa[x]]); if(tag[fa[x]]) down(fa[x]); if(tag[x]) down(x); int y=fa[x]; if(y!=goal) { if(getson(y)==getson(x)) rotate(y,goal); else rotate(x,goal); } rotate(x,goal); } update(x); }
原来写的代码没有下传标记A了,很神奇
⑤、rotate 里
if(y==goal) goal=x; else ch[z][ch[z][1]==y]=x;
而不是
if(z)ch[z][ch[z][1]==y]=x;
⑥、build里 update(mid)而不是update(f)
还有一点:这里所有有关父节点与子节点的操作,都不需要判断是否有子节点
why?
T3 hdu 1890 Robotic Sort
给出n的点,点有点权,i次操作
每次操作 找到点权第i小的点是第j个,翻转第i个到第j个,输出j
本题要输出k值在第几个位置
很容易想到根据序列建立完全平衡二叉树,然后查k值的位置
因为中序遍历就是序列,k值的位置就是k值的排名
排名怎么查?
以前写的都是递归,因为splay是依托权值大小关系建的,所以可以判断大小累计排名
但这里不是,是根据序列位置建的
我们无法判断点在点的左子树还是右子树
所以弃疗了。。。。。。
其实
把点转到根节点,左子树大小+1就是排名
做了一遍了,这里还想不到,果然还是弱。
#include<cstdio> #include<cstring> #include<algorithm> #define N 100010 using namespace std; int n,root; int sum[N],ch[N][2],fa[N]; bool tag[N]; struct node { int h,id; }e[N]; bool cmp(node p,node q) { if(p.h!=q.h) return p.h<q.h; return p.id<q.id; } void update(int x) { sum[x]=1; if(ch[x][0]) sum[x]+=sum[ch[x][0]]; if(ch[x][1]) sum[x]+=sum[ch[x][1]]; } void down(int x) { tag[x]^=1; if(ch[x][0]) tag[ch[x][0]]^=1; if(ch[x][1]) tag[ch[x][1]]^=1; swap(ch[x][0],ch[x][1]); } void build(int l,int r,int f) { if(l>r) return; int mid=l+r>>1; fa[mid]=f; ch[f][mid>f]=mid; sum[mid]=1; build(l,mid-1,mid); build(mid+1,r,mid); update(mid); } int find(int now,int x)//排名为x的是谁 { if(tag[now]) down(now); int tmp=0; if(ch[now][0]) tmp=sum[ch[now][0]]; if(x<=tmp) return find(ch[now][0],x); if(tmp+1==x) return now; return find(ch[now][1],x-tmp-1); } void rotate(int x,int &goal) { int y=fa[x],z=fa[y]; if(y==goal) goal=x; else ch[z][ch[z][1]==y]=x; int kind=ch[y][1]==x; ch[y][kind]=ch[x][!kind]; fa[ch[y][kind]]=y; ch[x][!kind]=y; fa[y]=x; fa[x]=z; update(y); } void splay(int x,int & goal) { while(x!=goal) { int y=fa[x],z=fa[y]; if(tag[z]) down(z); if(tag[y]) down(y); if(tag[x]) down(x); if(y!=goal) { if(ch[y][1]==x^ch[z][1]==y) rotate(x,goal); else rotate(y,goal); } rotate(x,goal); } update(x); } void rever(int l,int r) { int x=find(root,l-1),y=find(root,r+1); splay(x,root);splay(y,ch[root][1]); tag[ch[y][0]]^=1; } void clear() { memset(tag,0,sizeof(tag)); memset(fa,0,sizeof(fa)); memset(ch,0,sizeof(ch)); } int main() { while(1) { scanf("%d",&n); if(!n) return 0; clear(); for(int i=1;i<=n;i++) { scanf("%d",&e[i].h); e[i].id=i; } sort(e+1,e+n+1,cmp); build(1,n+2,0); root=n+3>>1; int pos; for(int i=1;i<n;i++) { splay(e[i].id+1,root); pos=sum[ch[root][0]]+1; printf("%d ",pos-1); rever(i+1,pos); } printf("%d\\n",n); } }
以上是关于伸展树复习的主要内容,如果未能解决你的问题,请参考以下文章