【ACWing】1063. 永无乡

题目地址:

https://www.acwing.com/problem/content/description/1065/

永无乡包含 n n n座岛,编号从 1 1 1 n n n,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n n n座岛排名,名次用 1 1 1 n n n来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 a a a出发经过若干座(含 0 0 0座)桥可以到达岛 b b b,则称岛 a a a和岛 b b b是连通的。现在有两种操作:
B x y表示在岛 x x x与岛 y y y之间修建一座新桥。
Q x k表示询问当前与岛 x x x连通的所有岛中第 k k k重要的是哪座岛,即所有与岛 x x x连通的岛中重要度排名第 k k k小的岛是哪座,请你输出那个岛的编号。

输入格式:
第一行是用空格隔开的两个正整数 n n n m m m,分别表示岛的个数以及一开始存在的桥数。接下来的一行是用空格隔开的 n n n个数,依次描述从岛 1 1 1到岛 n n n的重要度排名。随后的 m m m行每行是用空格隔开的两个正整数 a i a_i ai b i b_i bi,表示一开始就存在一座连接岛 a i a_i ai和岛 b i b_i bi的桥。后面剩下的部分描述操作,该部分的第一行是一个正整数 q q q,表示一共有 q q q个操作,接下来的 q q q行依次描述每个操作,操作的格式如上所述,以大写字母QB开始,后面跟两个不超过 n n n的正整数,字母与数字以及两个数字之间用空格隔开。

输出格式:
对于每个Q x k操作都要依次输出一行,其中包含一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 − 1 −1 1

数据范围:
对于 20 20 20的数据 n ≤ 1000 , q ≤ 1000 n≤1000,q≤1000 n1000,q1000
对于 100 100 100的数据 n ≤ 100000 , m ≤ n , q ≤ 300000 n≤100000,m≤n,q≤300000 n100000,mn,q300000

由于要寻找与某个岛 x x x的所有连通的岛里排名第 k k k的岛的编号,所以想到要用平衡树,一开始有 n n n个平衡树,然后每次合并两个树,构成更大的连通块(这可以用启发式合并来做,每次合并的时候将节点个数少的树后序遍历一遍,将每个节点插入到节点个数多的树里。注意,这里必须得后序遍历,这样插入的时候就可以直接拿原节点本身插入,而不需要new新节点了,可以节省很多空间。由于启发式合并里,每个节点参与合并的次数是 O ( log ⁡ n ) O(\log n) O(logn),因为每个节点所在的树的节点个数会一次次翻倍,合并一次它需要 O ( log ⁡ n ) O(\log n) O(logn),一共 n n n个节点,所以合并上花的总时间复杂度为 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n));并且,为了查询排名,可以将每个树节点里存一下它为根的子树的节点个数。这两者都可以用FHQ Treap来做。但是,光有平衡树还不够,给定岛屿编号,我们要能知道这个岛屿现在在哪棵树里,进而得到树根,然后才能进行查询和合并的操作,这就需要并查集。代码如下:

#include <iostream>
using namespace std;

// 开一倍空间即可
const int N = 1e5 + 10;
int n, m;
struct Node {
    
    
    int l, r;
    int val, id;
    int sz, rnd;
} tr[N];
int idx;

// p是并查集数组,root[i]表示i号岛屿所在的FHQ Treap的
// 树根节点下标,只有在i是并查集树根的时候才有效
int root[N], p[N];

int get_node(int val, int id) {
    
    
    tr[++idx].sz = 1;
    tr[idx].val = val;
    tr[idx].id = id;
    tr[idx].rnd = rand();
    return idx;
}

void pushup(int u) {
    
    
    tr[u].sz = tr[tr[u].l].sz + tr[tr[u].r].sz + 1;
}

int merge(int x, int y) {
    
    
    if (!x || !y) return x | y;
    if (tr[x].rnd > tr[y].rnd) {
    
    
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    } else {
    
    
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}

void split(int u, int val, int &x, int &y) {
    
    
    if (!u) x = y = 0;
    else {
    
    
        if (tr[u].val <= val) {
    
    
            x = u;
            split(tr[u].r, val, tr[u].r, y);
        } else {
    
    
            y = u;
            split(tr[u].l, val, x, tr[u].l);
        }

        pushup(u);
    }
}

// 在编号为id的岛屿所在的FHQ Treap里查询排名第rank的节点的id
int get_id(int id, int rank) {
    
    
    int u = root[id];
    while (u) {
    
    
        if (rank <= tr[tr[u].l].sz) u = tr[u].l;
        else if (rank > tr[tr[u].l].sz + 1) {
    
    
            rank -= tr[tr[u].l].sz + 1;
            u = tr[u].r;
        } else return tr[u].id;
    }

    return -1;
}

// 把下标为u的节点插入到b号岛屿所在的FHQ Treap里
void insert(int u, int b) {
    
    
    int x, y;
    // 按u的val将b所在的FHQ Treap分裂
    split(root[b], tr[u].val, x, y);
    // 再合并回来
    root[b] = merge(merge(x, u), y);
}

// 后序遍历u,将每个节点插入编号为b的岛屿所在的树里
void dfs(int u, int b) {
    
    
    if (!u) return;

    dfs(tr[u].l, b);
    dfs(tr[u].r, b);
    insert(u, b);
}

int find(int x) {
    
    
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
    
    
        int x;
        scanf("%d", &x);
        p[i] = root[i] = get_node(x, i);
    }

    while (m--) {
    
    
        int x, y;
        scanf("%d%d", &x, &y);
        x = find(x), y = find(y);
        if (x != y) {
    
    
            if (tr[root[x]].sz > tr[root[y]].sz) swap(x, y);
            p[x] = y;
            dfs(root[x], y);
        }
    }
    
    scanf("%d", &m);
    while (m--) {
    
    
        int x, y;
        char op[2];
        scanf("%s%d%d", op, &x, &y);
        x = find(x);
        if (*op == 'B') {
    
    
            y = find(y);
            if (x != y) {
    
    
                if (tr[root[x]].sz > tr[root[y]].sz) swap(x, y);
                p[x] = y;
                dfs(root[x], y);
            }
        } else {
    
    
            int k = y;
            if (tr[root[x]].sz < k) puts("-1");
            else printf("%d\n", get_id(x, k));
        }
    }

    return 0;
}

时间复杂度 O ( n log ⁡ 2 n + m log ⁡ n ) O(n\log^2n+m\log n) O(nlog2n+mlogn),空间 O ( n ) O(n) O(n)

也可以用Splay来做。代码如下:

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int n, m;
struct Node {
    
    
    int s[2], p, v, id;
    int sz;
    void init(int _v, int _id, int _p) {
    
    
        v = _v, id = _id, p = _p;
        sz = 1;
    }
} tr[N];
int root[N], idx;
int p[N];

int find(int x) {
    
    
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

void pushup(int x) {
    
    
    tr[x].sz = tr[tr[x].s[0]].sz + tr[tr[x].s[1]].sz + 1;
}

void rotate(int x) {
    
    
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x, int k, int b) {
    
    
    while (tr[x].p != k) {
    
    
        int y = tr[x].p, z = tr[y].p;
        if (z != k)
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }

    if (!k) root[b] = x;
}

void insert(int u, int b) {
    
    
    int c = root[b], v = tr[u].v, p = 0;
    while (c) p = c, c = tr[c].s[v > tr[c].v];
    if (p) tr[p].s[v > tr[p].v] = u;
    tr[u].p = p;
    // 插入完了要将插入的节点伸展到树根
    splay(u, 0, b);
}

int get_k(int k, int b) {
    
    
    int u = root[b];
    while (u) {
    
    
        if (k <= tr[tr[u].s[0]].sz) u = tr[u].s[0];
        else if (k > tr[tr[u].s[0]].sz + 1) {
    
    
            k -= tr[tr[u].s[0]].sz + 1;
            u = tr[u].s[1];
        } else return tr[u].id;
    }

    return -1;
}

void dfs(int u, int b) {
    
    
    if (!u) return;

    if (tr[u].s[0]) dfs(tr[u].s[0], b);
    if (tr[u].s[1]) dfs(tr[u].s[1], b); 
    insert(u, b);
}

int main() {
    
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
    
    
        p[i] = root[i] = i;
        int v;
        scanf("%d", &v);
        tr[i].init(v, i, 0);
    }
    idx = n;

    while (m--) {
    
    
        int a, b;
        scanf("%d%d", &a, &b);
        a = find(a), b = find(b);
        if (a != b) {
    
    
            if (tr[root[a]].sz > tr[root[b]].sz) swap(a, b);
            p[a] = b;
            dfs(root[a], b);
        }
    }

    scanf("%d", &m);
    while (m--) {
    
    
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        a = find(a);
        if (*op == 'B') {
    
    
            b = find(b);
            if (a != b) {
    
    
                if (tr[root[a]].sz > tr[root[b]].sz) swap(a, b);
                p[a] = b;
                dfs(root[a], b);
            }
        } else {
    
    
            if (tr[root[a]].sz < b) puts("-1");
            else printf("%d\n", get_k(b, a));
        }
    }

    return 0;
}

时空复杂度一样。

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/121529087
今日推荐