伸展树复习

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);
}
View Code

 

 

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");
}
View Code

 

列举代码中好几个错误:

①、答案的输出

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);
    }
}
View Code

 

以上是关于伸展树复习的主要内容,如果未能解决你的问题,请参考以下文章

css有用的代码片段

《树》之伸展树

高级数据结构实现——自顶向下伸展树

伸展树基本概念基本题目

poj2828 伸展树模拟

算法学习:伸展树(splay)