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

(我决定先把笔记从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]);
    //去右子树里找 
}

 

posted @ 2019-07-22 06:49  晔子  阅读(323)  评论(0编辑  收藏  举报