• 数据结构题目大赏 (一堆题目没做)


    (我决定先把笔记从word复制过来。。后面一边做题一边完善)

    树状数组3类应用

    TYPE 1 

    P1908 逆序对

    树状数组

    维护

    比6大的数=总数-比他小的

    a:19260817

    离散化

    原数组排序

    01126789

    Unique 去重(algorithm)

    上边安排查找

     

     TYPE 2

    树状数组维护x坐标

     

    二维偏序

    Xi<=xj

    Yi<=yj

    因为y是按照升序给出的,对于yj,

    3810 三维偏序

    Type 3

    怕不是树状数组板子

    C维护有多少个

    一开始读入就把数全部取模

    然后单点加减顺带上取模

    考虑如果这个数字加减操作完等于mod ,那就把包含它的那些c都加一

     

    开m个树状数组,每个内容都是01的

    第i个树状数组的j下表

    表示Aj%?=mod


     

    https://www.luogu.org/problemnew/show/P2234

    权值线段树,在权值上数组,而不是普通的数组

    建立线段树,维护区间最大值,最小值

    单点修改,区间查询

     

    https://www.cnblogs.com/xiaoyezi-wink/p/11105832.html

    线段树可以打多个lazytag

    CF718C Sasha and Array

    https://www.luogu.org/problemnew/show/CF718C

    自己百度Σ

    母函数,生成函数,推斐波那契数通项公式

    存的时候直接存feibo

    单点修改乘以矩阵快速幂

    转化成区间求一个数,区间求和

    乘以的是矩阵了

     

               

    https://www.luogu.org/problemnew/show/P4145

    开根号的性质

    单点修改开根号

    当是叶子节点才修改

     

    CF85D Sum of Medians

    https://www.luogu.org/problemnew/show/CF85D

    数组s只存五个数

     

    关注小数(长的小,不是非整数)

    线段树就是把所有加入过的数拿出来排序建线段树

    每个区间维护一个s表示当前时刻这个区间里存在多少数

    f[0...4], f[k]表示i mod 5 = k的所有ai的和

    合并的时候就是s=sl+sr, f[i]=fl[i]+fr[(i-sl)%5]

    下面没事干就考     线段树离线或者莫队

    只有查询没修改

     

    区间修改取min,打上标记

    难度在于把询问离线

     

    离线就是都读进来然后你想干啥干啥

    在线就是边读边处理

     

    P2757 [国家集训队]等差子序列

    https://www.luogu.org/problemnew/show/P2757

    直接让len=3好了

    线段树维护哈希

    1. 单点修改
    2. 比较两个区间是否同
    3. 线段树的每个节点

    NOI以下所有线段树

    想不开作死

     

    最简单

     

    NOIP线段树不会超过  中位数  这个难度

     然后Word 一堆待办


    二叉查找树

    二叉查找树的性质:

    对于每个节点,它左子树的所有节点都小于该节点,右子树的所有节点都大于该节点

    二叉搜索树查找:

    从根节点出发,查找大于这个点的元素,就往树的右边找,否则就往左边走

    锅:形态不固定,查找操作依赖于深度

    平衡树:基于元操作,旋转

    支持区间修改,区间查询

    主要实现方式 Splay,Treap

    Splay Tree 伸展树:通过伸展操作让树的深度不那么深

    比如把1转到根节点上,1的一个儿子跟着跑上去,另一个接到父节点的儿子上

    如果一个节点被访问过,那么接下来他被再次访问的几率更大

    Splay

    背景简介:

    伸展树(Splay Tree),是一种二叉搜索树,它能在 O(log n)内完成插入、查找和删除操作。

    它由丹尼尔·斯立特和罗伯特·恩卓·塔扬在 1985 年发明的。

    Splay的特点: 

    在伸展树上的一般操作都基于伸展操作:假设想要对一个二叉查找树执行一系列的查找操作,为了使整个查找时间更小,被查频率高的那些条目就应当经常处于靠近树根的位置。 于是想到设计一个简单方法, 在每次查找之后对树进行重构,把被查找的条目搬移到离树根近一些的地方。伸展树应运而生。伸展树是一种自调整形式的二叉查找树,它会沿着从某个节点到树根之间的路径,通过一系列的旋转把这个节点搬移到树根去。

    Splay的伸展: 

    • 如果当前点,父亲,爷爷呈一条直线,我们先转父亲再转自己。
    • 如果当前点,父亲,爷爷扭曲,我们连续转两次自己。

    为啥可以把这个树转来转去呢???因为对于一棵树,你随便提起一个节点都可以成为一棵新形态的树 

    splay最重要的是它的自适应的思想

    在树高没有任何限制(相对treap控制期望树高,RBT/AVL/WBT控制树高)的情况下还能保持均摊时间复杂度

    splay就是

    首先你需要旋转

    然后你需要知道把一个点双旋到根

    然后你就只需要在任意操作的最后把找到的点旋上来就行了

    P3369 【模板】普通平衡树

    https://www.luogu.org/problemnew/show/P3369

     

    int fa[N],ch[N][2]; //ch[n]  0是左儿子,1是右儿子 
    int cnt[N];  //记录n出现的次数
    int data[N]; //记录n的权值
    int siz[N];  //当前节点n及其子树一共多少个数 
    
    int son(int x)  //看x是它父亲的左儿子 0 ,还是右儿子 1  
    {
        return x==ch[fa[x]][1];
    }
    
    void pushup(int rt) //统计信息 ,上传siz 
    { 
        siz[rt]=siz[ch[rt][0]]+siz[ch[rt][1]]+cnt[rt];
    }
    
    void rotate(int x) //旋转操作,旋转之后仍然需要满足二叉查找树的性质 
    {
        int y=fa[x],z=fa[y];
        int b=son(x),c=son(y); //记录x,y分别是父亲的哪一边 
        int a=ch[x][!b];  //x的位置的逆儿子 
        
        if(z) ch[z][c]=x,fa[x]=z; //如果z存在 ,x的父亲变成z 
        else root=x; //z不存在,那么旋转之后x就成了根 
        
        
        if(a) fa[a]=y; ch[y][b]=a;
        //处理x,z的关系 ,看图 
        
        ch[x][!b]=y;fa[y]=x; //y到了相对于x的a的位置 
        //处理x,y的关系 ,看图 
        
        pushup(y);pushup(x); //上传节点信息 
    }
    //两个儿子,一个跟着上去,一个接下来 
    
    void splay(int x,int i)//x转移到i的儿子上 
    {  
        while(fa[x]!=i) //x不是i的儿子,就一直转 
        {
            int y=fa[x],z=fa[y];
            if(z==i) //如果i是x的爷爷,直接一次旋转操作 
            {
                rotate(x);
            }
            else
            {
                if(son(x)==son(y)) 
                //如果当前点,父亲,爷爷三点一线,先转父亲再转自己 
                {
                    rotate(y);rotate(x);
                }
                else
                //如果当前点,父亲,爷爷扭曲,就连续转两次自己 
                {
                    rotate(x);rotate(x);
                }
            }
        }
    }
    
    void insert(int &rt,int x){  //插入操作 
        if(rt==0) //这个节点从未出现过,新建点 
        {  
            rt=++nn;  //nn是总节点个数,rt就是节点编号 
            data[rt]=x; //赋值 
            siz[rt]=cnt[rt]=1;
            return;
        }
        if(x==data[rt])//如果x就等于当前节点,曾经出现过,+1就好
        {   
            cnt[rt]++;  //这个数的数量+1 
            siz[rt]++;  //子树节点个数+1 
            return;
        }
        if(x<data[rt])//x比当前节点小 
        {  
            insert(ch[rt][0],x);  //插入到左子树 
            fa[ch[rt][0]]=rt;  //标记左儿子的父亲是自己 
            pushup(rt);  //上传siz 
        }
        else//x大于当前节点 
        {
            insert(ch[rt][1],x);  //插入右子树 
            fa[ch[rt][1]]=rt;  //标记右儿子的父亲是自己 
            pushup(rt);  //上传siz 
        }
    }
    
    
    //找前驱后继 
    //前驱 ,小于x的最大值 
    int getpre(int rt,int x)
    {
        int p=rt,ans;  //p是当前节点的编号,ans是x的前驱 
        while(p)
        {
            if(x<=data[p]) //x比当前节点小,走左子树 
            {
                p=ch[p][0];
            }
            else  //否则去右子树 ,不断靠近最优前驱 
            {  
                ans=p;  
                p=ch[p][1];
            }
        }
        return ans;
    }
    
    //后继 ,大于x的最小值 
    int getsuc(int rt,int x)
    {
        int p=rt,ans;
        while(p)
        {
            if(x>=data[p])
            {
                p=ch[p][1];
            }
            else
            {
                ans=p;
                p=ch[p][0];
            }
        }
        return ans;
    }
    
    //找以rt为根的树的最小值 ,不断走左子树 
    int getmn(int rt)
    {
        int p=rt,ans=-1;
        while(p)
        {
            ans=p;
            p=ch[p][0];
        }
        return ans;
    }
    
    //删除权值为x的节点 
    void del(int rt,int x)
    {
        if(data[rt]==x) //找到了这个节点 ,准备删 
        {
            if(cnt[rt]>1)//节点不是一个,那就只删去一个 
            { 
                cnt[rt]--;
                siz[rt]--;
            }
            else  //节点只有一个 
            {
                splay(rt,0);  //把要删除的点旋转到根节点上(根节点编号为0) 
                int p=getmn(ch[rt][1]); //求出大于rt右儿子的最小值 
                if(p==-1) //没有找到,那么rt的右儿子就是大于rt的最小值 
                {
                    root=ch[rt][0];   //用比rt大的最小值代替rt ,删除rt 
                    fa[ch[rt][0]]=0;  //标记根节点 
                }
                else  //大于rt的最小值存在于rt右儿子的左子树中 
                {
                    splay(p,rt);  //把p转到rt的位置 
                    root=p;fa[p]=0; //最小值作为新的根 
                    ch[p][0]=ch[rt][0]; //rt的左儿子接到p的下面 
                    fa[ch[rt][0]]=p; //标记父亲 
                    pushup(p);  //上传siz 
                }
            }
            return;
        }
        if(x<data[rt]) //去左子树找 
        {
            del(ch[rt][0],x);
        }
        else
        {
            del(ch[rt][1],x);
        }
        pushup(rt);  //pushup统计信息 
    }
    
    int getk(int rt,int k) //求权值为k的节点排第几 
    {
        if(data[rt]==k) //当前节点就是k 
        {
            splay(rt,0); //把当前节点转到根上 
            if(ch[rt][0]==0) //如果它没有左子树,那么它rank 1 
            {
                return 1;
            }
            else  //不然的话它的rank就是左子树中所有节点数+1 
            {
                return siz[ch[rt][0]]+1;
            }
        }
        if(k<data[rt]) return getk(ch[rt][0],k);
        if(data[rt]<k) return getk(ch[rt][1],k);
    }
    
    int getkth(int rt,int k) //求排名第k的节点 
    {
        int l=ch[rt][0];  //找当前节点的左儿子 
        if(siz[l]+1<=k&&k<=siz[l]+cnt[rt]) return data[rt];
        //如果k比左子树节点多但是加上rt之后又比它少,那么rt就是排名第k的节点 
        if(k<siz[l]+1) return getkth(l,k);
        //去左子树里找 
        else return getkth(ch[rt][1],k-siz[l]-cnt[rt]);
        //去右子树里找 
    }
  • 相关阅读:
    连接mysql数据库,创建用户模型
    管理信息系统的开发与管理
    加载静态文件,父模板的继承和扩展
    从首页问答标题到问答详情页
    首页列表显示全部问答,完成问答详情页布局
    制作首页的显示列表
    发布功能完成
    登录之后更新导航
    完成登录功能,用session记住用户名
    完成注册功能
  • 原文地址:https://www.cnblogs.com/xiaoyezi-wink/p/11191528.html
Copyright © 2020-2023  润新知