习题课1-3(并查集、BST)

习题课1-3

等式

  • 第一行整数T,表示数据组数

  • 接下来会有T组数据,对于每组数据

  • 第一行是两个整数n,m,表示变量个数和约束条件个数

  • 接下来m行,每行三个整数a,b,e,表示第a个变量的和第b个变量的关系

  • 若e=0则表示第a个变量不等于第b个变量

  • 若e=1则表示第a个变量等于第b个变量

  • 并查集(union-find)

  • 将两个元素所在集合合并起来

  • 查询两个元素是否在同一个集合中

  • 注意e=1的操作要在e=0的操作之前

解法1

  • 第i个元素所在集合ID(i)
  • 把=1的条件提到前面来,相当于对约束条件排序
  • 每次执行e=1时,若相应的两个元素为a和b,那么将所有满足ID©==ID(a)或者ID©==ID(b)的元素合并起来,也就是令所有ID©==ID(a)
  • 之后执行e=0时,若相应的两个元素a和b有ID(a)==ID(b),则不合法,否则合法
  • 时间复杂度O(nm)

解法1+

  • set(i)表示编号为i的集合
  • 对于操作=1
    • 若set(id(a))的集合大小大于set(id(b)),交换a和b
    • set(id(b))的集合大小等于set(id(b))&(并)set(id(a)) (意思让b是a和b的并集),并且对于任意x属于set(id(a)),让id(x)等于id(b)
    • 白话意思就是小集合一个个拿出来加到大集合里面(启发式合并)
  • 用一个变长数组保存
  • C++:std::vector
  • java:ArrayList
  • python:list()

解法2

  • 路径压缩和启发式合并

  • Father:每个节点的父亲,路径压缩的

  • Rank:节点的秩,用来启发式合并的

  • 首先要排序,把所有等于1的操作换到前面来

  • int cnt = 0;
                // 把操作等于0的操作滞后,也可以把操作等于1的操作提前
                for (int i = 0; i < (m-cnt); i++) {
          
          
                    if (E.get(i) ==0){
          
          
                        while( E.get(m-1-cnt)==0 && (m-1-cnt)>i){
          
          
                            cnt++;
                        }
                        Collections.swap(A,i,m-1-cnt);
                        Collections.swap(B,i,m-1-cnt);
                        Collections.swap(E,i,m-1-cnt);
                    }
    
                }
    
  • 先找出A、B两个集合的根,路径压缩,(路径压缩,在往上寻找父亲节点的时候,把所有经过的节点都变成根节点(最早的祖先))

  • 对于0操作,如果相等,则返回NO,因为此时已经执行完1操作,已经保存好完整的并查集关系

  • 对于1操作,如果不相等,此时需要合并,father数组用于寻找根节点,rank表示并查集曾经达到的最大深度,

  •             for (int i = 0; i < m; i++) {
          
          
                    int setA = find(A.get(i));
                    int setB = find(B.get(i));
                    if (E.get(i) == 0){
          
          
                        if (setA==setB) {
          
          
                            return "No";
                        }
                    }else{
          
          
                        if (setA!=setB){
          
          
                            // 让B作为A的父节点
                            // 启发式合并
                            if (rank[setA]>rank[setB]){
          
          
                                int tmp = setA;
                                setA = setB;
                                setB = tmp;
                            }
                            father[setA] = setB;
                            if (rank[setA]==rank[setB]){
          
          
                                rank[setB]++;
                            }
    
                        }
                    }
                }
    

道路升级

  • 从图中选出任意条边,使得任意两点都互相连通

  • 并且任意两点的路径连起来的边权是最大的

  • 最后希望选出的边数尽可能少

  • 连通图(任意两点都可以互相连通)肯定有生成树,一定有最大生成树

  • 任何不在最大生成树上的边,加入其他的边,都不会导致连通路径的边权变得更大,所以可以丢弃

解法1

  • kruskal算法

  • 按边权大小从大到小排列,依次加入图中,用并查集维护连通性

  • disjoint(并查集英文名称)

  • 没有用启发式合并,只用了路径压缩

  • static class UnionSet {
          
          
                int[] f;
                void init(int n){
          
          
                    f = new int[n+1];
                    for (int i = 1; i <= n; i++) {
          
          
                        f[i] = i;
                        rank[i] = 0;
                    }
                }
    
                int find(int x){
          
          
                    return f[x]==x?x:(f[x]=find(f[x]));
                }
    
                boolean merge(int x,int y){
          
          
                    // 需要操作父亲节点,不然会丢失节点
                    int setX = find(x);
                    int setY = find(y);
                    if (setX!=setY){
          
          
                        f[setX] = setY;
                        return true;
                    }
                    return false;
                }
            }
    
  • 初始化并查集

  • 怎样循环?逆序循环,因为权值是从小到达排列的,然后合并,如果合并成功,就把这条边加入

  • 最后reverse边的集合,因为答案是正序输出的

  • if(find(x)!=find(y))

  • father(y) = father(x)

  • 这种写法会丢失x以上的父节点关系,所以是错误的

二叉查找树

  • 维护一个整数集合(数字可重复)
  • 插入一个数字
  • 删除一个数字
  • 查询某个数字有多少个
  • 查询最值
  • 查询某个数字的前驱

问题分析

  • 可用二叉树解决,为了保证时间复杂度,我们得使用平衡二叉树解决
  • BBST有很多种,Treap、Splay、红黑树、AVL、SBT
  • 推荐Treap(更优先)和Splay,前者好写速度快,后者好写功能强大

解法1

  • c++的std::multiset
  • java的TreeSet
  • python的set(),需自己加点操作使得可重复
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YdMXruPo-1605228918387)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20201111215106943.png)]
  • 1:插入一个数字
  • 2:删除一个数字
  • 3:输出这个数字有多少个
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XAnL8VP6-1605228918388)(C:\Users\liusiping\AppData\Roaming\Typora\typora-user-images\image-20201111215251113.png)]
  • 4:获取最小元素
  • 5:查询某个数字的前驱,(lower_bound,二分查找)
  • 找出它前面一个,找不到就输出None
  • 百度:平衡树模板

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/109632306
1-3