zoukankan      html  css  js  c++  java
  • 动态树问题与Link-Cut Trees学习笔记

    动态树问题

    维护一个动态森林,支持:

    • Link x, y 将x和y连接
    • Cut x, y 删除x与y之间的边
    • Query x, y 询问x和y是否在一棵树内

    Link_Cut Trees

    作为Tarjan神犇研究的玩意,命名和Union-Find Set如出一辙…

    具体描述见论文《QTree解法的一些研究,yangzhe,2007》,这里对一些容易引发误解的地方做出说明。

    基本结构

    就是splay的森林,u,v在同一棵splay内当且仅当他们在原树中位于同一条偏爱路径。(splay中)u在v左边当且仅当在原树中u在v上方。 不难证明森林和这里的“splay森林”构成一一对应的关系。由于splay的旋转不会改变splay中的左右关系,因而对应的splay森林不变,由于“一一对应”,原森林也不变。

    操作

    1. access(u):暴力将当前节点到根的路径变为偏爱路径
    2. make_rt(u):换根。将u设为其所在树的根。

      Make-rt(u)
          access u
          splay u to the root
          reverse the path which is u belonged

      这也就是让我(貌似还有很多人)迷惑的rev数组的由来,其作用就是用lazytag的思想翻转一条链。

    3. link(i, j):连接i, j

      Link(i, j)
          Make-rt i
          fa[i] = j
    4. cut(i, j):切断i和j间的路径

      Cut(i, j)
          Make-rt i
          access j
          splay j
          fa[i] = chl[j][0] = 0

      这里换根将两种情况同一考虑了。

    5. cut-fa(i): 切断i和父亲的路径

      Cut-fa(i)
          access i
          splay i
          fa[chl[i][0]] = 0
          chl[i][0] = 0

      对于维护有根树的情景,由于不能随意变动根,要采取这样的方法。

    时间复杂度和代码复杂度

    用一些妙不可言的方法可以证明所有操作都是摊还 O(lgn) 的,但是常数极大..由于是摊还时间所以在生活中很难真正应用,OI里自然不怕(除非出题人是sbt脑残粉专门写交互题卡splay…)。

    代码总的来说还算清晰,和链剖复杂度类似,不过用起来比链剖要灵活的多了。用yangzhe神犇的话说,就是 “不在意局部的平衡而关注全局平衡” 带来的优势。

    几个模板题源码

    SDOI Cave 洞穴勘测

    DS脑残系列1,闹不懂rev啥意思导致2hDebug…

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 10005;
    struct Link_Cut_Tree {
        int fa[maxn], chl[maxn][2], rev[maxn];
        int stk[maxn], top;
        bool is_root(int nd)
        { return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
        void push_down(int nd)
        {
            if (!rev[nd]) return;
            int &lc = chl[nd][0], &rc = chl[nd][1];
            if (lc) rev[lc] ^= 1;
            if (rc) rev[rc] ^= 1;
            rev[nd] = 0;
            swap(lc, rc);
        }
        void zig(int nd)
        {
            int p = fa[nd], g = fa[p];
            int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
            if (!is_root(p)) chl[g][tg] = nd;
            fa[son] = p, fa[nd] = g, fa[p] = nd;
            chl[p][tp] = son, chl[nd][tp^1] = p;
        }
        void splay(int nd)
        {
            stk[top = 1] = nd;
            for (int i = nd; !is_root(i); i = fa[i]) stk[++top] = fa[i];
            while (top) push_down(stk[top--]);
            while (!is_root(nd)) {
                int p = fa[nd], g = fa[p];
                int tp = chl[p][0] != nd, tg = chl[g][0] != p;
                if (is_root(p)) {zig(nd); break;}
                else if (tp == tg) zig(p), zig(nd);
                else zig(nd), zig(nd);
            }
        }
        void access(int x)
        {
            for (int y = 0; x; x = fa[y = x])
                splay(x), chl[x][1] = y;
        }
        inline void make_rt(int nd)
        { access(nd); splay(nd); rev[nd] ^= 1; }
        void link(int i, int j)
        {
            make_rt(i); fa[i] = j;
            access(i);
        }
        void cut(int i, int j)
        {
            make_rt(i); access(j); splay(j);
            fa[i] = chl[j][0] = 0;
        }
        int find(int i)
        {
            access(i); splay(i);
            while (chl[i][0]) i = chl[i][0];
            return i;
        }
    }lct;
    
    char s[20];
    int n, m;
    int u, v;
    int main()
    {
        freopen("sdoi2008_cave.in", "r", stdin);
        freopen("sdoi2008_cave2.out", "w", stdout);
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= m; i++) {
            scanf("%s%d%d", s, &u, &v);
            if (s[0] == 'C') lct.link(u, v);
            else if (s[0] == 'D') lct.cut(u, v);
            else {
                if (lct.find(u) == lct.find(v)) puts("Yes");
                else puts("No");
            }
        }
        return 0;
    }
    

    ZJOI Count 树的统计

    DS脑残系列2, rev[nd]误作rev导致1.5h的debug…

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 30005;
    int stk[maxn], top;
    int chl[maxn][2], fa[maxn], mx[maxn], sum[maxn], rev[maxn], rt[maxn];
    int n, m;
    char s[20];
    inline bool isrt(int nd)
    { return chl[fa[nd]][0] != nd && chl[fa[nd]][1] != nd; }
    
    void pdw(int nd)
    {
        if (!rev[nd]) return;
        int &lc = chl[nd][0], &rc = chl[nd][1];
        if (lc) rev[lc] ^= 1;
        if (rc) rev[rc] ^= 1;
        rev[nd] = 0, swap(lc, rc);
    }
    
    
    void dfs()
    {
        for (int nd = 1; nd <= n; nd++)
        printf("%d -- %d(%d,%d), sum = %d, mx = %d, rev = %d
    ", nd, fa[nd], chl[nd][0], chl[nd][1], sum[nd], mx[nd], rev[nd]);
        puts("---");
    }
    inline void update(int nd)
    {
        sum[nd] = sum[chl[nd][0]] + sum[chl[nd][1]] + rt[nd];
        mx[nd] = max(max(mx[chl[nd][0]], mx[chl[nd][1]]), rt[nd]);
    }
    
    void zig(int nd)
    {
        int p = fa[nd], g = fa[p];
        int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
        if (!isrt(p)) chl[g][tg] = nd;
        fa[nd] = g, fa[p] = nd, fa[son] = p;
        chl[nd][tp^1] = p, chl[p][tp] = son;
        update(p), update(nd);
    }
    
    void splay(int nd)
    {
        stk[top = 1] = nd;
        for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
        for (; top; top--)pdw(stk[top]);
        while (!isrt(nd)) {
            int p = fa[nd], g = fa[p];
            int tp = chl[p][0] != nd, tg = chl[g][0] != p;
            if (isrt(p)) { zig(nd); break; }
            else if (tp == tg) zig(p), zig(nd);
            else zig(nd), zig(nd);
        }
    }
    
    void access(int x)
    {
        for (int y = 0; x; x = fa[y = x])
            splay(x), chl[x][1] = y, update(x);
    }
    
    void mkt(int nd)
    { access(nd); splay(nd); rev[nd] ^= 1; }
    
    void link(int u, int v)
    { mkt(u); fa[u] = v; }// 连接
    void split(int u, int v)
    { mkt(u);access(v); splay(v); } // 提取区间
    
    
    int x[maxn], y[maxn];
    int main()
    {
        sum[0] = 0, mx[0] = INT_MIN;
        scanf("%d", &n);
        int u, v;
        for (int i = 1; i < n; i++)
            scanf("%d%d", &x[i], &y[i]);
        for (int i = 1; i <= n; i++) scanf("%d", &rt[i]), sum[i] = mx[i] = rt[i];
        for (int i = 1; i < n; i++) link(x[i], y[i]);
        scanf("%d", &m);
        for (int i = 1; i <= m; i++) {
            scanf("%s%d%d", s, &u, &v);
            if (s[1] == 'H') {
                splay(u);
                rt[u] = v;
                update(u);
            } else if (s[1] == 'M') split(u, v), printf("%d
    ", mx[v]);
            else if (s[1] == 'S') split(u, v), printf("%d
    ", sum[v]);
        }
        return 0;
    }

    HNOI BOUNCE 弹飞绵羊

    DS脑残系列3, 维护有根树贸然换根导致1.5hDebug……

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 200005;
    int chl[maxn][2], fa[maxn], rev[maxn], siz[maxn];
    int n, m;
    int stk[maxn], top = 0;
    inline void update(int nd)
    { siz[nd] = siz[chl[nd][0]]+siz[chl[nd][1]]+1;
      //cout << nd << "--" << siz[nd] << " " << siz[chl[nd][0]] << " " << siz[chl[nd][1]] << endl;
     }
    inline bool isrt(int nd)
    { return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
    void pdw(int nd)
    {
        if (!rev[nd]) return;
        int &lc = chl[nd][0], &rc = chl[nd][1];
        if (lc) rev[lc] ^= 1;
        if (rc) rev[rc] ^= 1;
        rev[nd] = 0;
        swap(lc, rc);
    }
    
    
    void zig(int nd)
    {
        int p = fa[nd], g = fa[p];
        int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
        if (!isrt(p)) chl[g][tg] = nd;
        fa[nd] = g, fa[p] = nd, fa[son] = p;
        chl[nd][tp^1] = p, chl[p][tp] = son;
        update(p); update(nd);
    }
    
    void splay(int nd)
    {
        stk[top = 1] = nd;
        for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
        for (; top; top--) pdw(stk[top]);
        while (!isrt(nd)) {
            int p = fa[nd], g = fa[p];
            if (isrt(p)) { zig(nd); break; }
            int tp = chl[p][0] != nd, tg = chl[g][0] != p;
            if (tp == tg) zig(p), zig(nd);
            else zig(nd), zig(nd);
        }
    }
    
    void access(int x)
    {
        for (int y = 0; x; x = fa[y = x])
            splay(x), chl[x][1] = y, update(x);
    }
    
    void mkt(int x)
    { access(x); splay(x); rev[x] ^= 1;
    }
    
    void link(int x, int y)
    {
        //cout << "link " << x << " " << y << endl;
        mkt(x);
        fa[chl[x][0]] = 0;
        chl[x][0] = 0;
        fa[x] = y;
        update(x);
        //access(x);
    }
    
    int ask(int nd)
    {
        access(nd); splay(nd);
        return siz[chl[nd][0]]+1;
    }
    
    void dfs(int nd, int tab = 0)
    {
        if (!nd) return;
        for (int i = 1; i <= tab; i++) putchar(' ');
        printf("%d--%d, siz = %d
    ", nd, fa[nd], siz[nd]);
        dfs(chl[nd][0], tab+2);
        dfs(chl[nd][1], tab+2);
    }
    
    int ki[maxn];
    
    int main()
    {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) siz[i] = 1;
        for (int i = 1; i <= n; i++) {
            scanf("%d", &ki[i]);
            if (ki[i] && i+ki[i] <= n) link(i, i+ki[i]);
        ///    for (int i = 1; i <= n; i++)
        ///        printf("%d-%d(%d, %d), siz = %d;  ", i, fa[i], chl[i][0], chl[i][1], siz[i]);
        ///    puts("");
        }
        scanf("%d", &m);
        for (int i = 1; i <= m; i++) {
            int opt, u, v;
            scanf("%d", &opt);
            if (opt == 1) {
                scanf("%d", &u); u++;
                printf("%d
    ", ask(u));
            } else if (opt == 2){
                scanf("%d%d", &u, &v);u++;
                ki[u] = v;
                if (ki[u] && u+ki[u] <= n) link(u, u+ki[u]);
                else link(u, 0);
            } else {
                scanf("%d", &u); u++;
                access(u); splay(u);
                dfs(u);
            }
        }
        return 0;
    }

    NOI2014 魔法森林

    第一次LCT 1A!
    不过还是因为把nd写成maxn调试了半天…以后MAXN一定用大写…

    思路就是用lct维护mst,将边按a排序,然后逐个尝试加入。如果成环就删除环上最大的节点。时间复杂度为O((n+m)lg(n+m)),虽然常数巨大但还是比不要脸的动态加边spfa高到不知哪里去。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 200005;
    int chl[maxn][2], fa[maxn], mx[maxn], rev[maxn], tp = 0; // 拆边用节点
    int rt[maxn];
    int stk[maxn], top = 0;
    inline bool isrt(int nd)
    { return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
    void pdw(int nd)
    {
        if (!rev[nd]) return;
        int &lc = chl[nd][0], &rc = chl[nd][1];
        if (lc) rev[lc] ^= 1; if (rc) rev[rc] ^= 1;
        rev[nd] = 0; swap(lc, rc);
    }
    void dfs()
    {
        for (int i = 1; i <= tp; i++)
        printf("%d-%d(%d, %d), rt = %d, mx = %d, rev = %d
    ", i, fa[i], chl[i][0], chl[i][1], rt[i], mx[i], rev[i]);
        puts("...........");
    }
    void update(int nd)
    {
        //cout << "Updating : " << nd << endl;
        int lc = chl[nd][0], rc = chl[nd][1];
        if (rt[mx[lc]] > rt[mx[rc]]) mx[nd] = mx[lc];
        else mx[nd] = mx[rc];
        if (rt[nd] > rt[mx[nd]]) mx[nd] = nd;
    }
    void zig(int nd)
    {
        int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, son = chl[nd][tp^1];
        if (!isrt(p)) chl[g][chl[g][0]!=p] = nd;
        fa[son] = p, fa[p] = nd, fa[nd] = g;
        chl[p][tp] = son, chl[nd][tp^1] = p;
        update(p), update(nd);
    }
    void splay(int nd)
    {
        // cout << "Splaying : " << nd << endl;
        stk[top = 1] = nd;
        for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
        while (top) pdw(stk[top--]);
        while (!isrt(nd)) {
            int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, tg = chl[g][0] != p;
            if (isrt(p)) { zig(nd); break; }
            if (tp == tg) zig(p), zig(nd);
            else zig(nd), zig(nd);
        }
        // dfs();
    }
    void access(int nd)
    {
        for (int i = 0; nd; nd = fa[i = nd])
            splay(nd), chl[nd][1] = i, update(nd);
    }
    
    void make_rt(int nd)
    { access(nd); splay(nd); rev[nd] ^= 1; }
    void link(int i, int j)
    { make_rt(i); fa[i] = j; }
    void cut(int i, int j)
    { make_rt(i); access(j); splay(j); fa[i] = chl[j][0] = 0; }
    void del(int i)
    { make_rt(i); fa[chl[i][0]] = fa[chl[i][1]] = 0, chl[i][0] = chl[i][1] = 0; }
    int find_rt(int nd)
    { access(nd), splay(nd); while (chl[nd][0]) nd = chl[nd][0]; return nd; }
    bool linked(int i, int j)
    { return find_rt(i) == find_rt(j); }
    void insert(int i, int j, int k)
    { rt[++tp] = k, link(i, tp), link(tp, j);}
    void split(int i, int j) // 提取区间
    { make_rt(i); access(j); splay(j); }
    
    int n, m;
    struct edge {
        int x, y, a, b;
        void print()
        {
            printf("%d,%d --> %d,%d
    ", x, y, a, b);
        }
    } egs[maxn];
    
    int read()
    {
        int a = 0, c;
        do c = getchar(); while(!isdigit(c));
        while (isdigit(c)) {
            a = a*10+c-'0';
            c = getchar();
        }
        return a;
    }
    
    int cmp(const edge &a, const edge &b)
    { return a.a < b.a; }
    
    int main()
    {
        memset(rt, -127/3, sizeof rt);
        scanf("%d%d", &n, &m);
        tp = n;
        for (int i = 1; i <= m; i++)
            egs[i].x = read(), egs[i].y = read(), egs[i].a = read(), egs[i].b = read();
        sort(egs+1, egs+m+1, cmp);
        int ans = INT_MAX, now_a = 0;
        for (int i = 1; i <= m; i++) {
            if (!linked(egs[i].x, egs[i].y)) insert(egs[i].x, egs[i].y, egs[i].b), now_a = egs[i].a;
            else {
                split(egs[i].x, egs[i].y);
                int mxnd = mx[egs[i].y]; // 最大节点
                if (egs[i].b < rt[mxnd]) {
                    del(mxnd);
                    insert(egs[i].x, egs[i].y, egs[i].b);
                    now_a = egs[i].a;
                }
            }
            if (linked(1, n)) {
                split(1, n);
                ans = min(ans, now_a + rt[mx[n]]);
            }
        }
        if (ans == INT_MAX) puts("-1");
        else cout << ans << endl;
        return 0;
    }
  • 相关阅读:
    HDU 4924 Football Manager(状压DP)
    android 为图片去灰
    关于invalidate和postInvalidate
    有点感想人月神话
    ObjectiveC语法快速参考
    Paint类 主要方法介绍
    关于游戏开发中的碰撞检测(转)
    两集合求交集的算法比较(转)
    Java编程中“为了性能”需做的26件事(转)
    android WebView总结(转)
  • 原文地址:https://www.cnblogs.com/ljt12138/p/6684342.html
Copyright © 2011-2022 走看看