永无乡「HNOI2012」

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

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

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

如果是单一的一个问题 求\(a_1\sim a_n\)中第\(k\)小的数是什么 显然是可以用一棵权值线段树直接实现的 这里要在岛之间连边 即把两个连通块合并 可以用一个线段树合并的奇技淫巧

什么是线段树合并?简单来说 就是把两个线段树上对应的点的权值相加得到新的线段树
一般需要线段树合并的题 如果直接建树 空间复杂度都会十分巨大 所以一般会使用动态开点 合并时注意一下新树节点的左右儿子
放张图理解一下线段树合并
永无乡「HNOI2012」

线段树合并代码

int merge_tr(int idx, int idy) {
    if (!idx) return idy;
    if (!idy) return idx;
    tr[idx].val += tr[idy].val; //权值
    tr[idx].lc = merge_tr(tr[idx].lc, tr[idy].lc); //左儿子
    tr[idx].rc = merge_tr(tr[idx].rc, tr[idy].rc); //右儿子
    return idx;
}

//root[x] = merge(root[x], root[y]);

所以操作就非常简单了 并查集维护连通性 每次把两个连通块连起来就合并这两棵权值线段树作为新连通块的树 查询第\(k\)大就常规操作就行

【代码】

#include <iostream>
#include <cstdio>
using namespace std;

struct segtree{
    int lc, rc, val;
}tr[6000005];

int n, m, q, a[100005], rk[100005], rt[100005], tot, fa[100005];
char tp[15];

void update(int &ind, int l, int r, int x) {
    if (!ind) ind = ++tot;
    if (l == r) {
        tr[ind].val = 1;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) update(tr[ind].lc, l, mid, x);
    if (x > mid) update(tr[ind].rc, mid+1, r, x);
    tr[ind].val = tr[tr[ind].lc].val + tr[tr[ind].rc].val;
}

void init() {
    for (int i = 1; i <= n; i++) {
        update(rt[i], 1, n, a[i]);
    }
    for (int i = 1; i <= n; i++) {
        fa[i] = i;
    }
}

int get(int x) {
    if (fa[x] == x) return x;
    else return fa[x] = get(fa[x]);
}

int merge_tr(int idx, int idy) {
    if (!idx) return idy;
    if (!idy) return idx;
    tr[idx].val += tr[idy].val;
    tr[idx].lc = merge_tr(tr[idx].lc, tr[idy].lc);
    tr[idx].rc = merge_tr(tr[idx].rc, tr[idy].rc);
    return idx;
}

void merge(int x, int y) {
    int tx = get(x), ty = get(y);
    if (tx != ty) {
        fa[ty] = tx;
        rt[tx] = merge_tr(rt[tx], rt[ty]);
    }
}

int query(int ind, int l, int r, int k) {
    if (l == r) return l;
    int mid = (l + r) >> 1;
    if (tr[tr[ind].lc].val >= k) return query(tr[ind].lc, l, mid, k);
    else return query(tr[ind].rc, mid+1, r, k - tr[tr[ind].lc].val);
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        rk[a[i]] = i;
    }
    init();
    for (int i = 1; i <= m; i++) {
        int x, y;
        scanf("%d %d", &x, &y);
        merge(x, y);
    } 
    scanf("%d", &q);
    for (int i = 1; i <= q; i++) {
        scanf("%s", tp);
        int x, y;
        scanf("%d %d", &x, &y);
        if (tp[0] == 'B') {
            merge(x, y);
        } else {
            int tx = get(x);
            if (tr[rt[tx]].val < y) puts("-1"); 
            else printf("%d\n", rk[query(rt[tx], 1, n, y)]);
        }
    }
    return 0;   
} 
上一篇:题解 [HNOI2012]集合选数


下一篇:P3224 [HNOI2012]永无乡 并查集+线段树合并