zoukankan      html  css  js  c++  java
  • 【暑假培训1】【知识】7.15

    数据结构

    • 下面是天天见面的:
      • 栈、队列
      • 并查集
      • 树状数组
      • 线段树
      • 平衡树 (splay)
    • 下面是不常见的:
      • 主席树
      • 树链剖分
      • 树套树
    • 下面是清北学堂课程表里的:
      • ST表
      • LCA
      • HASH

    1.堆

    堆的话是之前就学过的东西,也写过博客,可以带过:

    【堆】好多好多基础知识 【堆】【洛谷例题】p1090 p1334 p1177

    STL queu中有priority_queue,默认是一个大根堆,关于如何修改priority_queu的排序顺序,可以参见:【细小碎的oi小知识点总结贴】不定时更新(显然也没人看qwq)

    2.LCA:

    基本用途:求树上最短路

    求LCA的朴素算法:

    首先处理深度;

    然后:

    1. 如果deep(A)<deep(B) 交换AB
    2. 把A向上抬升,到B的深度;
    3. A和B同时上调,直到A=B;

    复杂度O(deep(tree)) 

    对于随机构造的树,可能这种算法还可以;但是当数据别出心裁构造的伐,如果退化成一条链,显然就凉凉了~;

    所以我们考虑优化:

    既然一个个跳不行,我们可不可以一次跳多个?

    显然可以!

    但是如果单纯的枚举一次跳多少个的伐,当数据够大时,我们枚举的范围可能是上百万的,也不够优;

    想到,任何一个数都可以表示为二进制,也就表示每个数都可以拆成若干个2的乘方相加;

    令p[x][i]表示x的2^i的祖先是谁,那么我们可以知道p[x][0]表示x的父亲,通过此可以想到递推方程: p[x][i]=p[p[x][i-1]][i-1];x的2^i祖先,等于x的2^(i-1)的祖先的2^(i-1)的祖先:

    基本流程与上面相同,但是我们不再是一个一个的往上跳。从乘方最大的的开始枚举,如果跳上去以后父亲不同就往上跳,父亲相同不跳:

    A和B的祖先开始是不一样的,从LCA开始,以上都一样,但是我们无法确定LCA,但是我们可以确定最后一个不一样的;

    伪代码:

    for i=16~0 
    if(p[A][i]!=p[B][i]) A=p[A][i];B=p[B][i];

    LCA一般用于解决一类可差分的问题,比如求AB之间的最短路径:求出A到根的路径,B到根的路径,减去两倍根到LCA的路径;

    3.ST表:

    用于求解没有修改的区间最大值问题

    mx[i][j]表示从i~i+2j-1的最大值

     求一个区间[l,r]的最大值,可以拆分为两部分:

    首先一个区间[l,r],求出区间长度:k=l-r;

    然后我们记录p=log2k;

    求这个区间的最大值,即为:max(mx[l][p],mx[r-2p+1][p]);

    关于mx数组的求法:递推:mx[i][j]=max(mx[i][j-1],mx[i+2j-1][j-1]);

    举个栗子:

    [19,46]

    k=46-19=28;p=16;ans=max(mx[19][4],mx[31][4])//可能写炸了将就着;

    4.Hash:

    Hash(字符串)=>int

    Hash将一个字符串转换为一个int;

    Hash是允许冲突的,但是我们要尽量避免冲突;

    设计Hash:

    1.随便取一个p,把字符串当做p进制数(建议取质数,并且是大于字符串大小的质数)

    2.取字符的ASCII码*p^i(i表示此字符在此字符串中是第几位[from right to left]),然后计算一段字符串的每个字符,把值相加,就是这一段字符串的Hash值;

    3.显然你看这个数会很大,可能会炸。我们可以取一个大质数mod用来取模,当然也可以用Unsigned long long强势自然溢出;

    具体的处理:

    求字符串,每个前缀的哈希值;

    Hi:1~i的Hash值;

    Hi=Hi-1*p+Si

    Hash(i,j)=Hj-Hi*pj-i+1

    5.并查集:

    令fa[x]表示x的父亲;树根的父亲等于本身;

    查询A和B在不在一个集合中:找到A和B的根,比较是否相同;

    如何将两个集合合并:

    找A代表元AA,B代表元BB,令fa[AA]=BB;

    路径压缩:直接指向代表元

    6.树状数组:

    浅谈树状数组

    支持单点修改,区间查询;其实也可以区间修改,单点查询(利用差分)

    所求所有问题,必须存在逆元?

    主要应用:

    1.  线段树常数过大时

    2.  线段树功能过多时

    可以在logn的时间内改变一个数,logn的时间求前缀和:

    然后只讲代码实现,具体原理↑:

    短短几行代码:

    int lowbit(int x){
        return x&(-x);
    }
    
    void modify(int x,int y){
        // add y to a[x]
        for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
    }
    
    int query(int x){
        // sum of a[1]...a[x]
        int ret=0;
        for(int i=x;i;i-=lowbit(i)) ret+=c[i];
        return ret;
    }
    
    int query(int l,int r){
        return query(r)-query(l-1);
    }

    7.线段树(抽象线段二叉树;):

    线段树小记

    支持区间修改,区间查询;

    主要应用:一类区间修改区间查询的问题;

    Sum线段树:

    先是单点修改:

    1. 确认点的位置
    2. 更新树的权值

    区间查询:

    任何一条线段,都可以被logn条线段覆盖;

    递归看关系;

    从最大的开始看,然后有关系就继续往下递归直到被完全覆盖,返回,统计进答案;

    线段树的节点个数2n级别的,*4防止溢出写炸;

    前置:

    struct Node{
        int l,r;
        int sum;
        int tag;
    }t[N<<2];
    
    void pushup(int rt){
        t[rt].sum=t[rt<<1].sum+t[rt<<1|1].sum;
    }

    建树:

    void build(int rt,int l,int r){
        t[rt].l=l;
        t[rt].r=r;
        if(l==r){
            t[rt].sum=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(rt<<1,l,mid);
        build(rt<<1|1,mid+1,r);
        pushup(rt);
    }

    单点修改a[p]=c;

    void modify(int rt,int p,int c){
        if(t[rt].l==t[rt].r){
            t[rt].sum=c;
            return;
        }
        pushdown(rt);
        int mid=(t[rt].l+t[rt].r)>>1;
        if(p<=mid) modify(rt<<1,p,c);
        else modify(rt<<1|1,p,c);
        pushup(rt);
    }

    区间查询:

    int query(int rt,int l,int r){
        if(l<=t[rt].l&&t[rt].r<=r){
            return t[rt].sum;
        }
        pushdown(rt);
        int ret=0;
        int mid=(t[rt].l+t[rt].r)>>1;
        if(l<=mid) ret+=query(rt<<1,l,r);
        if(mid<r) ret+=query(rt<<1|1,l,r);
        return ret;
    }

    区间修改:

    懒标记:

    标记这一整段线段的每一个值,都加一个特定的数;

    懒惰的:先放在哪里,在没有必要下方时,就不下放:标记下放:pushdown;

    Pushdown:

    将此点的标记下放给左右儿子,然后自己的标记清0;

    然后同时维护左右节点的区间和;

    碰到这个节点就下放

    void add(int rt,int l,int r,int c){
        if(l<=t[rt].l&&t[rt].r<=r){
            t[rt].tag+=c;
            t[rt].sum+=c*(t[rt].r-t[rt].l+1);
            return;
        }
        pushdown(rt);
        int mid=(t[rt].l+t[rt].r)>>1;
        if(l<=mid) add(rt<<1,l,r,c);
        if(mid<r) add(rt<<1|1,l,r,c);
        pushup(rt);
    }

    总结啦:

    • 堆:最大值插入、删除、查询
    • ST表:区间最大值查询
    • 树状数组:单点修改,前缀查询
    • 线段树:区间修改,区间查询
  • 相关阅读:
    vscode调试pomelo和pomelo使用vscode调试
    linux修改单个进程的系统时间
    python之路目录
    python之前端
    python之ORM操作
    python之redis和memcache操作
    python之消息队列
    python之协程与IO操作
    python之进程与线程
    python之网络编程
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11190497.html
Copyright © 2011-2022 走看看