zoukankan      html  css  js  c++  java
  • 清北学堂(2019 4 29 ) part 2

    主要内容数据结构:

    1.二叉搜索树

      一棵二叉树,对于包括根节点在内的节点,所有该节点左儿子比此节点小,所有该节点右儿子比该节点大,(感觉好像二分...)

      每个节点包含一个指向父亲的指针,和两个指向儿子的指针。如果没有则为空。每个节点还包含一个key值,代表他本身这个点的权值

    常用操作:

    插入一个数,删除一个数,询问最大/最小值,询问第k大值。

    插入操作:

    现在我们要插入一个权值为x的节点。
    为了方便,我们插入的方式要能不改变之前整棵树的形态。
    首先找到根,比较一下key[root]x,如果key[root] < x,节点应该插在root右侧,否则再左侧。看看root有没有右儿子,如果没有,那么直接把root的右儿子赋成x就完事了。
    否则,为了不改变树的形态,我们要去右儿子所在的子树里继续这一操作,直到可以插入为止。

    删除操作:

    要删掉一个权值,首先要知道这个点在哪。

    root开始,像是插入一样找权值为x的点在哪。

    定义x的后继y,是x右子树中所有点里,权值最小的点。
    找这个点可以x先走一次右儿子,再不停走左儿子。
    如果yx的右儿子,那么直接把y的左儿子赋成原来x的左儿子,然后用y代替x的位置。

    找第k大的数:

    对每个节点在多记一个size[x]表示x这个节点子树里节点的个数。

    从根开始,如果右子树的size k,就说明第k大值在右侧,往右边走。

    如果右子树size + 1 = k,那么说明当前这个点就是第k大值。

    否则,把k减去右子树size + 1,然后递归到左子树继续操作。

    那么为什么呢?

    证明:

      由二叉搜索树性质可得,左边字数一定比其父节点小,右边则反之

      看看正在处理的点的右子树根节点,对于其“size”,即节点个数,如果大于k,说明要找的树(或地址)肯定在右边

      因为左边的恒比右边小,如果此时size>=k,说明右面至少还有k个没找过的数,我们要找的是第k大的数,并不能允许右边有多于k个比目标大的数,那么向右找

      当找到当前节点的右子树根节点size+1=k时,说明这个数底下(包括自身)有k-1个比当前处理节点大的数,那么当前处理节点就是第k大的

      如果没等找到size+1=k就size<k了怎么办?

      说明右边比当前处理节点大的数并不足k,此时应向左找,找比当前节点小的数,也许时不时地向右子树找一下,最终找到第k大的数

      (我因为仔细读问题,没看见第k“大”的数...当第k个(小)的数证的,百思不得其解...)

    2.二叉堆

    一只神仙提前讲过,我之前也整过,然而用的优先队列,因为对就是棵完全二叉树,(看题目,“二叉”堆吖~)

    一直没有手写堆,今天试试。

    代码:

    #include<bits/stdc++.h>        //本代码致力于维护小根堆
    using namespace std;
    int a[100005];
    int n;
    int size;
    inline void up(int t){         //把数据上浮
        while(a[t]<a[t>>1]&&t){
            swap(a[t],a[t>>1]);
            t>>=1;
        }
    }
    inline void down(int t){       //下沉
        while((t<<1)<=size){
            int l=t*2;
            int r=t*2+1;
            if(r>size){            //对于某些奇怪的只有左儿子的树的特判
                if(a[t]>a[l]){
                    swap(a[t],a[l]);
                    t=l;
                }
                break;
            }
            if(a[t]<=a[l]&&a[t]<=a[r])break;
            else if(a[l]<a[r]){    //判断把哪个儿子拿上来当爹
                swap(a[t],a[l]);
                t=l;
            }
            else{
                swap(a[t],a[r]);
                t=r;
            }
        }
    }
    inline void pop(){     //弹出堆顶
        swap(a[1],a[size]);
        size--;
        down(1);
    }
    inline void add(int now){ //添加新元素
        a[++size]=now;
        up(size);
    }
    int main(){
        scanf("%d",&n);
        while(n){
            n--;
            int x;
            scanf("%d",&x);
            add(x);
        }
        while(size){
            printf("%d ",a[1]);
            pop();
        }
        return 0;
    }
    p.s.我为了确保这玩意对,特意又交了一遍快排模板

    (我写这玩意用了一晚上(并不怕笑话...),老师说下课时我差点脏话出口...我发誓这种情况不多见)

    3.区间RMQ问题

    举个例子(例题):

    给出一个序列,每次询问区间最大值.

    看上去暴力模拟能过吖...

    然而
    N 100000, Q 1000000.

    所以我们需要一套好算法,比如这个RMBRMQ,

    主要思路是将总区间不断二分,根据这些区间进行计算,如图:

    然后将所需访问区间拆分,但能保留的尽量大的区间一定要保留,因为大区间问问可以表示成许多小区间,只算大区间显然更快

    延迟更新:

    信息更新时,未必要真的做彻底的更新,可以只是将应该如何更新记录下来,等到真正需要查询准确信息时,才去更新 足以应付查询的部分。
    在区间增加时,如果要加的区间正好覆盖一个节点,则增加其节 点的inc值和sum值,不再往下走.在区间询问时,还是采取正常的区间分解.
    在上述两种操作中,如果我们到了区间[L, R]还要接着往下走,并且inc0,说明子区间的信息是不对的,我们将inc传送到左儿子和右儿子上,并将inc赋成0,即完成了一次更新.

    延迟更新可避免每次都赋值的麻烦情况,可极大省时

    4.并查集

    等我做出来村村通再整...

    先整下按秩合并:

    对每个顶点,再多记录一个当前整个结构中最深的点到根的深度deepx.
    注意到两个顶点合并时,如果把比较浅的点接到比较深的节点上.
    如果两个点深度不同,那么新的深度是原来较深的一个.
    只有当两个点深度相同时,新的深度是原来的深度+1.
    注意到一个深度为x的顶点下面至少有2x个点,所以x至多为log N.
    那么在暴力向上走的时候,要访问的节点至多只有log个 。

    然而路径压缩更好...虽有两种算法一起用的sao操作,但为了避免一种“玄学错误”(???)而并不这么用(起码用的肥肠少...)

    5.树及LCA问题

    在一棵有根树中,树上两点x, yLCA指的是x, y向根方向遇到到第一个相同的点.
    我们记每一个点到根的距离为deepx.
    注意到x, y之间的路径长度就是deepx + deepy - 2 * deepLCA

    两个点到根路径一定是前面一段不一样,(汇合)后面都一样.
    注意到LCA的深度一定比x, y都要小.
    利用deep,把比较深的点往父亲跳一格,直到x, y跳到同一个点上.
    这样做复杂度是O(len).
    首先不妨假设deepx < deepy.
    为了后续处理起来方便,我们先把x跳到和y一样深度的地方.
    如果xy已经相同了,就直接退出.
    否则,由于xyLCA的距离相同,倒着枚举步长,如果x, y的第2j个父亲不同,就跳上去.这样,最后两个点都会跳到离LCA距离为1的地方,在跳一步就行了.
    时间复杂度O(N log N). 

    我..累了(QAQ)

  • 相关阅读:
    PAT 乙级真题 1013.组个最小数
    PAT 乙级真题 1012.D进制的A+B
    PAT 乙级真题 1011.个位数统计
    PAT 乙级真题 1010.月饼
    PAT 乙级真题 1009.1019.数字黑洞
    PAT 乙级真题 1008.锤子剪刀布
    PAT 乙级真题 1007.A除以B
    PAT 乙级真题 1006.1016.部分A+B
    C++自定义sort函数
    VS2017如何使用scanf函数
  • 原文地址:https://www.cnblogs.com/648-233/p/10792824.html
Copyright © 2011-2022 走看看