伸展树复习

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?

时间: 2024-10-12 16:55:47

伸展树复习的相关文章

伸展树复习 (bzoj 1251 序列终结者)

本来要看LCT的,确发现自己弱得连splay都忘记了,复习一发,顺便重写一发 关键点: 1. 伸展树为左小右大的二叉树,所以旋转操作不会影响树的性质 2. 区间操作为: int u = select(L - 1), v = select(R + 1); splay(u, 0); splay(v, u); //通过旋转操作把询问的区间聚集到根的右子树的左子树下 因为伸展树为左小右大的二叉树,旋转操作后的所以对于闭区间[L, R]之间的所有元素都聚集在根的右子树的左子树下 因为闭区间[L, R],

[转] Splay Tree(伸展树)

好久没写过了,比赛的时候就调了一个小时,差点悲剧,重新复习一下,觉得这个写的很不错.转自:here Splay Tree(伸展树) 二叉查找树(Binary Search Tree)能够支持多种动态集合操作.因此,在信息学竞赛中,二叉排序树起着非常重要的作用,它可以被用来表示有序集合.建立索引或优先队列等. 作用于二叉查找树上的基本操作的时间是与树的高度成正比的.对一个含n各节点的完全二叉树,这些操作的最坏情况运行时间为O(log n).但如果树是含n个节点的线性链,则这些操作的最坏情况运行时间

【BBST 之伸展树 (Splay Tree)】

最近“hiho一下”出了平衡树专题,这周的Splay一直出现RE,应该删除操作指针没处理好,还没找出原因. 不过其他操作运行正常,尝试用它写了一道之前用set做的平衡树的题http://codeforces.com/problemset/problem/675/D,运行效果居然还挺好的,时间快了大概10%,内存少了大概30%. 1 #include <cstdio> 2 #include <cstring> 3 #include <string> 4 #include

poj_3468 伸展树

题目大意 一个数列,每次操作可以是将某区间数字都加上一个相同的整数,也可以是询问一个区间中所有数字的和.(这里区间指的是数列中连续的若干个数)对每次询问给出结果. 思路 1. 伸展树的一般规律 对于区间的查找更新操作,可以考虑使用伸展树.线段树等数据结构.这里使用伸展树来解决.     伸展树对数组进行维护的核心思想是,将需要维护的一组数单独提取出来,形成一棵子树(一般为整棵树的根节点的右子节点的左孩子节点 为根),然后再这个子树上进行操作.此时进行某些操作(如 ADD, SUM 等),只需要在

HYSBZ 1503 郁闷的出纳员 伸展树

题目链接: https://vjudge.net/problem/26193/origin 题目描述: 中文题面....... 解题思路: 伸展树, 需要伸展树的模板, 突然发现自己昨天看到的模板不是太好, 现在又新找了一个,  很简练, 自己将模板的实现从头到尾看了一遍, 觉得数组实现的实在是非常的巧妙, 然后自己照着敲了一遍, 边敲边看, 崩掉了.....肯定是哪里手残了, 没有必要浪费时间去改了, 有那时间不如看点别的 代码: #include <iostream> #include &

伸展树整理

伸展树 1.在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置.因此,在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方.伸展树应运而生.伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去. 2.操作 (1)伸展操作 伸展操作Splay(x,S)是在保持伸展树有序性的前提下,通过一系列旋转将伸展树S中的元素x调整至树的

Splay伸展树

伸展树,感觉对我这种菜鸟还是有些吃力,主要也是旋转的时候吧,把要查询的节点旋转到根节点,看网上是有两种方法,一是从上到下,一是从下到上.从上到下,是把树拆分成中树,左树和右树,始终保证目标节点在中树,不断向下把中树的节点添到右树或者左树,直到目标节点为中树的根,再将三树合并起来.另外一种从下到上,旋转操作类似AVL树中的旋转,但是判断好像不是很好写,我写的是从上到下的,另外删除也很巧妙,先把目标节点旋转到根,若此时左子树为空直接删除,否则找到左子树最右的节点当头,利用伸展树的特殊旋转就可以一步删

HNOI2002(伸展树)

营业额统计 Time Limit:5000MS     Memory Limit:165888KB     64bit IO Format:%lld & %llu Submit Status Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每天的营业额.分析营业情况是一项相当复杂的工作.由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动

伸展树

伸展树(Splay Tree)树平衡二叉查找树的一种,具有二叉查找树的所有性质.在性能上又比普通的二叉查找树有所改进:普通的二叉查找树在最坏情况下的查找操作的时间复杂度为O(n)(当二叉树退化成一条链的时候),而伸展树在任何情况下的平摊时间复杂度均为 O(log2n). 特性 和普通的二叉查找树相比,具有任何情况下.任何操作的平摊O(log2n)的复杂度,时间性能上更好 和一般的平衡二叉树比如 红黑树.AVL树相比,维护更少的节点额外信息,空间性能更优,同时编程复杂度更低 在很多情况下,对于查找