zoukankan      html  css  js  c++  java
  • 最小生成树 (MST)

    // 没错就是所有人都会的那个算法

    定义略去,见 Wiki

    Cut Property

    选择图的任意一个割,则其中权值最小的边 (e(u, v)) 一定在某些 MST 中。

    证明:假设有一个不包含 (e) 的 MST (T)(T+e) 包含了一个简单环,且 ((u, v)) 经过了这个割集若干次,删除 ((u, v)) 中最小割边,则一定会得到一个权值小于等于 (T) 的 MST。因此若 (e) 为严格最小,则所有 MST 均包含 (e),否则一定有某些 MST 包含 (e)

    求 MST 的算法

    三种算法都容易用 Cut Property 证明。

    Kruskal: 将边排序,贪心地加入。 (O(m log m))

    Prim: 在一个连通块的基础上每次扩展最小边,复杂度根据堆实现而定。

    Boruvka's Algorithm

    初始时没有任何边,因此 (n) 个点就会有 (n) 个连通块。

    重复若干轮操作。每轮操作对于每个连通块,选择一条权值最小的与其他块相连的边连接,然后重新计算连通块。

    显然每次操作至少会使连通块数目减少一半,复杂度为 (O(m log n)) (如果用 DFS 维护连通块)

    因为暴力操作就可以保证复杂度,这种思想可以在很多边权由一些公式计算的完全图中应用。

    貌似可以证明对于有重复边权的边的图,这个算法也是对的。

    一些题目

    AT3611 Tree MST

    有简单的点分治做法。

    直接暴力用 Boruvka 求解。

    每次进行换根 (up and down) DP,求解出到每个点距离最近的连通块,求出前两个,这样如果第一个就是本身可以取第二个,方便 DP。

    具体方法就是先 DFS 一遍求子树答案,再 DFS 一遍将父亲和兄弟的答案加上。

    复杂度 (O(n log n)),代码用了并查集,(O(n alpha(n) log n))

    #include <cstdio>
    #include <cctype>
    #include <algorithm>
    #include <vector>
    #include <cassert>
    using namespace std;
    #define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
    typedef long long ll;
    template<typename T> inline void gi(T &x){
      char ch; x = 0;
      int f = 1;
      while(isspace(ch = getchar()));
      if(ch == '-') ch = getchar(), f = -1;
      do x = x * 10 + ch - '0'; while(isdigit(ch = getchar()));
      x *= f;
    }
    template<class T> void upmax(T &x, T y){x = x>y ? x : y;}
    template<class T> void upmin(T &x, T y){x = x<y ? x : y;}
    
    const int N = 200005;
    
    int fa[N], rk[N], tot = 0;
    int find(int x){
      return !fa[x] ? x : fa[x] = find(fa[x]);
    }
    void merge(int x, int y){
      // printf("Merge components : %d %d
    ", x, y);
      x = find(x), y = find(y);
      if(x == y) return;
      --tot;
      if(rk[x] < rk[y]) swap(x, y);
      fa[y] = x; rk[x] += rk[x] == rk[y];
    }
    
    struct Edge{
      int v, w;
      Edge(int _v, int _w) : v(_v), w(_w) {};
    };
    struct Data{
      ll v1; int c1;
      ll v2; int c2;
    };
    const ll inf = 0x3FFFFFFFFFFFFFFFLL;
    const Data INF = {inf, 0, inf, 0};
    Data operator+=(Data &a, const Data &b){
      if(b.v1 < a.v1){
        if(b.c1 != a.c1) a.v2 = a.v1, a.c2 = a.c1;
        a.v1 = b.v1, a.c1 = b.c1;
      }
      else if(b.v1 < a.v2 && b.c1 != a.c1)
        a.v2 = b.v1, a.c2 = b.c1;
      if(b.v2 < a.v2 && b.c2 != a.c1)
        a.v2 = b.v2, a.c2 = b.c2;
      
      return a;
    }
    Data inc(Data x, int d){
      x.v1 += d; x.v2 += d;
      return x;
    }
    vector<Edge> G[N];
    int w[N];
    Data f[N], mined[N];
    
    void dfs1(int x, int fa){
      f[x] = INF;
      for(auto e : G[x]){
        if(e.v == fa) continue;
        dfs1(e.v, x);
        f[x] += inc(f[e.v], e.w);
      }
      f[x] += {w[x], find(x), inf, 0};
    }
    void dfs2(int x, int fa){
      Data s = f[x];
      for(auto e : G[x]){
        if(e.v == fa) continue;
        f[e.v] += inc(s, e.w);
        s += inc(f[e.v], e.w);
      }
      s = INF;
      for(auto e = G[x].rbegin(); e != G[x].rend(); ++e){
        if(e->v == fa) continue;
        f[e->v] += inc(s, e->w);
        s += inc(f[e->v], e->w);
        dfs2(e->v, x);
      }
    }
    
    int main(){
      int n;
      gi(n);
      for(int i=1; i<=n; i++) gi(w[i]);
      for(int i=1; i<n; i++){
        int x, y, w;
        gi(x), gi(y), gi(w);
        G[x].emplace_back(y, w); G[y].emplace_back(x, w);
      }
      tot = n;
      ll res = 0;
      while(tot != 1){
        fill(mined + 1, mined + 1 + n, INF);
        dfs1(1, 1);
        dfs2(1, 1);
        for(int i=1; i<=n; i++) mined[find(i)] += inc(f[i], w[i]);
        for(int i=1; i<=n; i++){
          int rt = find(i);
          if(rt != i) continue;
          if(find(mined[rt].c1) == rt){
            merge(rt, mined[rt].c2);
            res += mined[rt].v2;
          }
          else{
            merge(rt, mined[rt].c1);
            res += mined[rt].v1;
          }
        }
      }
      printf("%lld
    ", res);
      return 0;
    }
    

    [CF888G] Xor-MST

    可以直接把点权扔到 01-Trie 里做 Borvuka,每次对于一个连通块,删掉块内在暴力查询,复杂度 (O(nw log n))(w) 为二进制字长,但比较难写,常数很大。

    有更巧妙的做法。把所有点权扔到 01-Trie 里,发现应该尽量连接高位相同的,进而两个叶子会在它们的 LCA 处被合并。

    在 Trie 上做分治,到一个分支点后,把两边子树合并成两个连通块,在两边子树里面选一个 xor 值最小的,连一条边合并起来就好。

    正确性根据 xor 的性质和 Cut Property 就好。复杂度不变。

    #include <cstdio>
    #include <cctype>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    using namespace std;
    #define File(s) freopen(s".in", "r", stdin), freopen(s".out", "w", stdout)
    typedef long long ll;
    namespace io {
      const int SIZE = (1 << 21) + 1;
      char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
      #define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
      char getc () {return gc();}
      inline void flush () {fwrite (obuf, 1, oS - obuf, stdout); oS = obuf;}
      inline void putc (char x) {*oS ++ = x; if (oS == oT) flush ();}
      template <class I> inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
      template <class I> inline void print (I x) {if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while (x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr --]);}
      struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
    }
    using io :: gi; using io :: putc; using io :: print; using io :: getc;
    template<class T> void upmax(T &x, T y){x = x>y ? x : y;}
    template<class T> void upmin(T &x, T y){x = x<y ? x : y;}
    const int N = 200005, W = 32;
    int c[N * W][2], nc = 0;
    ll res = 0;
    void insert(int val){
      int p = 0;
      for(int i=29; i>=0; i--){
        int v = (val >> i) & 1;
        if(!c[p][v]) c[p][v] = ++nc;
        p = c[p][v];
      }
    }
    int query(int x, int y, int k){
      if(!c[x][0] && !c[x][1]) return 0;
      int ret = 0x7fffffff;
      if(c[x][0]){
        if(c[y][0]) upmin(ret, query(c[x][0], c[y][0], k - 1));
        else upmin(ret, query(c[x][0], c[y][1], k - 1) | (1 << k));
      }
      if(c[x][1]){
        if(c[y][1]) upmin(ret, query(c[x][1], c[y][1], k - 1));
        else upmin(ret, query(c[x][1], c[y][0], k - 1) | (1 << k));
      }
      return ret;
    }
    void dfs(int x, int k){
      if(k < 0) return;
      if((int)(c[x][0] != 0) + (int)(c[x][1] != 0) == 1){
        dfs(c[x][0] | c[x][1], k - 1);
        return;
      }
      res += query(c[x][0], c[x][1], k - 1) | (1 << k);
      dfs(c[x][0], k - 1); dfs(c[x][1], k - 1);
    }
    
    int main(){
      int n;
      gi(n);
      for(int i=1; i<=n; i++){
        int x; gi(x);
        insert(x);
      }
      dfs(0, 29);
      printf("%lld
    ", res);
      return 0;
    }
    

    咕咕咕

  • 相关阅读:
    matplotlib
    Android 集成 支付宝支付
    android 自动化测试案例之 MonkeyRunner
    android 自动化测试案例之 MonkeyScript
    Android 使用自定义Drawable 设置圆角矩形或者圆形图片
    android 仿微信朋友圈图片选择控件
    Android 自定义控件之 日期选择控件
    android 和 js 交互
    android 蓝牙连接端(客户端)封装
    android 项目集成 微信支付
  • 原文地址:https://www.cnblogs.com/RiverHamster/p/advanced-mst.html
Copyright © 2011-2022 走看看