动态树问题与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\n", 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\n", mx[v]);
        else if (s[1] == 'S') split(u, v), printf("%d\n", 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\n", 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\n", 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\n", 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\n", 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;
}
posted @ 2017-02-25 22:17  ljt12138  阅读(194)  评论(0编辑  收藏  举报