0
点赞
收藏
分享

微信扫一扫

Link Cut Tree 应用三

柠檬果然酸 2022-02-06 阅读 30

1. 题目链接:P2542 [AHOI2005] 航线规划

思路 L C T LCT LCT 加并查集解决,时间复杂度 O ( n l o g   n ) O(nlog\ n) O(nlog n) 。我们发现正序删边是不好来维护连通性的,基于正难则反原则,我们可以离线处理,逆序完成操作。显然,每个点可以代表一个双连通分量,查询就是链的长度减一,也就是桥的数量。连接一条边,如果在 L C T LCT LCT 中还没连通就 l i n k link link ,如果连通了,这里会出现一个环,然后我们暴力缩点,可以把当前辅助树(也就是并查集形成的树)的根结点当作集合的标志结点,之后 d f s dfs dfs 整个辅助树,把链上的其他点的并查集暴力改成这个标志结点,最后再断开标志节点与子树的连接,也就是标志结点表示了整棵子树。总的暴力修改次数不会超过 n l o g   n nlog\ n nlog n 次。同时要注意 a c c e s s access access 的时候要更新 x x x F i n d ( f [ x ] ) Find(f[x]) Find(f[x])

#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'

using namespace std;

const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 3e4 + 5, M = 1e5 + 5;

#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])

int fa[N], ch[N][2], siz[N];
int f[N], a[M], b[M];
int op[M], ans[M];
bool rev[N], vis[M];
struct node {
    int x, y;
    bool operator < (const node& r) const {
        return x < r.x || (x == r.x && y < r.y);
    }
}e[M];
inline int ident (int x, int y) { return rs(y) == x; }
inline int find(int x) {
    if (x == f[x]) return x;
    return f[x] = find(f[x]);
}
inline bool nroot (int x) {
    return ls(fa[x]) == x || rs(fa[x]) == x;
}
inline void pushup (int x) { 
    siz[x] = siz[ls(x)] + siz[rs(x)] + 1;
}
inline void pushrev (int x) {
    swap (ls(x), rs(x));
    rev[x] ^= 1;
}
inline void pushdown (int x) {
    if (rev[x]) {
        if (ls(x)) pushrev (ls(x));
        if (rs(x)) pushrev (rs(x));
        rev[x] = 0;
    }
}
inline void rotate (int x) {
    int y = fa[x], z = fa[y], k = ident (x, y);
    if (nroot (y)) ch[z][ident (y, z)] = x;
    ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
    if (ch[y][k]) fa[ch[y][k]] = y;
    fa[x] = z, fa[y] = x;
    pushup (y), pushup (x);
}
int st[N], top;
inline void splay (int x) {
    int r = x;
    st[top = 1] = r;
    while (nroot (r)) st[++ top] = r = fa[r];
    while (top) pushdown (st[top --]);
    while (nroot (x)) {
        int y = fa[x], z = fa[y];
        if (nroot (y)) 
            rotate (ident (x, y) ^ ident (y, z) ? x : y);
        rotate (x);
    }
    pushup (x);
}
inline void access (int x) {
    for (int y = 0; x; y = x, x = fa[y] = find(fa[x]))
        splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) {
    access (x);
    splay (x);
    pushrev (x);
}
inline int findroot (int x) {
    access (x), splay (x);
    while (ls(x)) pushdown (x), x = ls(x);
    splay (x);
    return x;
}
inline void split (int x, int y) {
    makeroot (y);
    access (x);
    splay (x);
}
inline void del (int x, int y) {
    if (! x) return;
    f[x] = y;
    del (ls(x), y);
    del (rs(x), y);
}
inline void merge (int x, int y) {
    if (x == y) return ;
    makeroot (x);
    if (findroot (y) != x) {
        fa[x] = y;
        return ;
    }
    del (rs(x), x);
    rs(x) = 0, pushup(x);
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int n, m, cnt, x, y, tot;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) siz[i] = 1, f[i] = i;
    for (int i = 1; i <= m; i ++) {
        cin >> x >> y;
        if (x > y) swap (x, y);
        e[i] = {x, y};
    }
    sort (e + 1, e + m + 1);
    for (cnt = 1; cin >> op[cnt] && op[cnt] != -1; cnt ++) {
        cin >> x >> y;
        if (! op[cnt]) {
            if (x > y) swap (x, y);
            vis[lower_bound (e + 1, e + 1 + m, (node){x, y}) - e] = 1;
        }
        a[cnt] = x, b[cnt] = y;
    }
    for (int i = 1; i <= m; i ++) 
        if (! vis[i]) merge (find(e[i].x), find(e[i].y));
    for (cnt --, tot = 0; cnt; cnt --) {
        x = find(a[cnt]), y = find(b[cnt]);
        if (op[cnt]) split (x, y), ans[++ tot] = siz[x] - 1;
        else merge (x, y);
    }
    while (tot) cout << ans[tot --] << endl;
    return 0;
}

2. 题目链接:P4172 [WC2006]水管局长

思路 L C T LCT LCT 离线解决,时间复杂度 O ( n l o g   n ) O(nlog\ n) O(nlog n) 。我们发现删边操作很难实现,于是倒着处理所有询问,删边就变成了加边。然后查询所有路径上最大值的最小值,不难发现就是要维护这个图的最小生成树,然后就可以直接查询树路径上的最大值了,可以用 L C T LCT LCT 做到。从而问题转化为 动态维护最小生成树 。难度比上一题较易。

#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'

using namespace std;

const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 1e3 + 5, M = 2e5 + 5;

#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])

int n, m, qur;
map<pair<int, int>, int> id;
struct edge { int u, v, w; } e[M];
struct que { int op, u, v, flag; } q[M];
auto cmp = [](edge a, edge b)->bool{return a.w < b.w;};
int fa[M], ch[M][2];
int mx[M], val[M], ans[M];
bool rev[M], vis[M];
inline bool isroot (int x) { return ls(fa[x]) != x && rs(fa[x]) != x; }
inline void pushup (int x) {
    mx[x] = val[x];
    if (e[mx[x]].w < e[mx[ls(x)]].w) mx[x] = mx[ls(x)];
    if (e[mx[x]].w < e[mx[rs(x)]].w) mx[x] = mx[rs(x)]; 
}
inline void pushrev (int x) {
    swap (ls(x), rs(x));
    rev[x] ^= 1;
}
inline void pushdown (int x) {
    if (rev[x]) {
        if (ls(x)) pushrev (ls(x));
        if (rs(x)) pushrev (rs(x));
        rev[x] = 0;
    }
}
int st[M], top;
inline void rotate (int x) {
    int y = fa[x], z = fa[y], k = rs(y) == x;
    if (! isroot (y)) ch[z][rs(z) == y] = x;
    ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
    if (ch[y][k]) fa[ch[y][k]] = y;
    fa[x] = z, fa[y] = x;
    pushup (y), pushup (x);
}
inline void splay (int x) {
    int r = x;
    st[top = 1] = r;
    while (! isroot (r)) st[++ top] = r = fa[r];
    while (top) pushdown (st[top --]);
    while (! isroot (x)) {
        int y = fa[x], z = fa[y];
        if (! isroot (y))
            rotate ((rs(y) == x) ^ (rs(z) == y) ? x : y);
        rotate (x);
    }
    pushup (x);
}
inline void access (int x) { 
    for (int y = 0; x; x = fa[y = x])
        splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) { access (x), splay (x), pushrev (x); }
inline void split (int x, int y) { makeroot (x), access (y), splay (y); }
inline void link (int x, int y) { makeroot (x), fa[x] = y; }
inline void cut (int x, int y) { split (x, y), ls(y) = fa[x] = 0; }
inline int findroot (int x) { 
    access (x), splay (x);
    while (ls(x)) pushdown (x), x = ls(x);
    return x;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m >> qur, e[0].w = 0;
    for (int i = 1; i <= m; i ++) {
        cin >> e[i].u >> e[i].v >> e[i].w;
        if (e[i].u > e[i].v) swap (e[i].u, e[i].v);
    } 
    for (int i = 1; i <= n; i ++) val[i] = mx[i] = 0;
    for (int i = 1; i <= m; i ++) val[i + n] = mx[i + n] = i;
    sort (e + 1, e + 1 + m, cmp);
    for (int i = 1; i <= m; i ++) id[make_pair(e[i].u, e[i].v)] = i;
    for (int i = 1; i <= qur; i ++) {
        cin >> q[i].op >> q[i].u >> q[i].v;
        if (q[i].u > q[i].v) swap (q[i].u, q[i].v);
        if (q[i].op == 2) {
            q[i].flag = id[make_pair(q[i].u, q[i].v)];
            vis[q[i].flag] = true;
        }
    }
    
    for (int i = 1; i <= m; i ++) {
        if (vis[i]) continue;
        int u = e[i].u, v = e[i].v;
        if (findroot (u) == findroot (v)) continue;
        link (u, i + n), link (v, i + n);
    }
    for (int i = qur; i >= 1; i --) {
        int u = q[i].u, v = q[i].v;
        split (u, v);
        if (q[i].op == 1) {
            ans[i] = e[mx[v]].w;
        }else {
            int t = q[i].flag, d = mx[v];
            if (e[t].w < e[d].w) {
                cut (e[d].u, d + n), cut (e[d].v, d + n);
                link (u, t + n), link (v, t + n);
            }
        }
    }
    for (int i = 1; i <= qur; i ++) 
        if (q[i].op == 1) cout << ans[i] << endl;
    return 0;
}

3. 题目链接:P2387 [NOI2014] 魔法森林

思路 L C T LCT LCT 解决,时间复杂度 O ( n l o g   n ) O(nlog\ n) O(nlog n) 。直接求解比较麻烦。我们考虑按照 a i a_i ai 为关键字将每一条边排序,然后按顺序动态加入每一条边,这样我们每次固定 a i a_i ai ,利用 L C T LCT LCT 维护 b i b_i bi 的最大值的最小值。这里三题都用到了一个把边权转化为点权的技巧,我们将边在 L C T LCT LCT 理解为点,我们插入两条边一个点,从而实现将边插入。现在我们的问题变成了求解出现环的问题,我们考虑贪心,假设我们现在要连边 ( u , v ) (u, v) (u,v) ,但 u u u v v v 之间已经存在一条路径。这样就必须在 u u u v v v 的路径中选一条边断开。由于操作后要求得最小代价,因此要在 u u u v v v 的路径中,选择一条边权最大的边断开后再连入边 ( u , v ) (u, v) (u,v) ,当然这些都是在 u u u v v v 已经连通的情况下,不连通时我们直接连上即可。

#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define re register
#define endl '\n'

using namespace std;

const int MOD = 998244353;
const int inf = 0x3f3f3f3f;
const double eps = 1e-8;
const double Pi = acos(-1.0);
const int N = 2e5 + 5;

#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])

int n, m;
struct edge { int x, y, a, b; } e[N];
auto cmp = [](edge u, edge v)->bool{ return u.a < v.a; };
int fa[N], ch[N][2], mx[N], val[N];
bool rev[N];

inline bool isroot (int x) { return ls(fa[x])!=x && rs(fa[x])!=x; }
inline void pushrev (int x) {
    swap (ls(x), rs(x));
    rev[x] ^= 1;
}
inline void pushdown (int x) {
    if (rev[x]) {
        if (ls(x)) pushrev (ls(x));
        if (rs(x)) pushrev (rs(x));
        rev[x] = 0;
    }
}
inline void pushup (int x) {
    mx[x] = val[x];
    if (e[mx[x]].b < e[mx[ls(x)]].b) mx[x] = mx[ls(x)];
    if (e[mx[x]].b < e[mx[rs(x)]].b) mx[x] = mx[rs(x)];
}
inline void rotate (int x) {
    int y = fa[x], z = fa[y], k = rs(y) == x;
    if (! isroot (y)) ch[z][rs(z) == y] = x;
    ch[y][k] = ch[x][k ^ 1], ch[x][k ^ 1] = y;
    if (ch[y][k]) fa[ch[y][k]] = y;
    fa[x] = z, fa[y] = x;
    pushup (y), pushup (x);
}
int st[N], top;
inline void splay (int x) {
    int r = x;
    st[top = 1] = r;
    while (! isroot (r)) st[++ top] = r = fa[r];
    while (top) pushdown (st[top --]);
    while (! isroot (x)) {
        int y = fa[x], z = fa[y];
        if (! isroot (y)) 
            rotate ((rs(y) == x) ^ (rs(z) == y) ? x : y);
        rotate (x);
    }
    pushup (x);
}
inline void access (int x) {
    for (int y = 0; x; x = fa[y = x]) 
        splay (x), rs(x) = y, pushup (x);
}
inline void makeroot (int x) { access (x), splay (x), pushrev (x); }
inline void split (int x, int y) { makeroot (x), access (y), splay (y); }
inline void link (int x, int y) { makeroot (x), fa[x] = y; }
inline void cut (int x, int y) { split (x, y), fa[x] = ls(y) = 0; }
inline int findroot (int x) {
    access (x), splay (x);
    while (ls(x)) pushdown (x), x = ls(x);
    return x;
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= m; i ++) 
        cin >> e[i].x >> e[i].y >> e[i].a >> e[i].b;
    sort (e + 1, e + 1 + m, cmp);
    for (int i = 1; i <= n; i ++) mx[i] = val[i] = 0;
    for (int i = 1; i <= m; i ++) mx[i + n] = val[i + n] = i;
    int ans = inf;
    for (int i = 1; i <= m; i ++) {
        int u = e[i].x, v = e[i].y;
        if (findroot (u) == findroot (v)) {
            split (u, v);
            int t = mx[v];
            if (e[i].b < e[t].b) {
                cut (e[t].x, t + n), cut (e[t].y, t + n);
                link (u, i + n), link (v, i + n);
            }
        }else link (u, i + n), link (v, i + n);
        split (1, n);
        if (findroot (1) == findroot (n)) 
            ans = min (ans, e[i].a + e[mx[n]].b);
    }
    if (ans == inf) ans = -1;
    cout << ans << endl;
    return 0;
}
举报

相关推荐

0 条评论