[HNOI2012]永无乡——[splay合并]

11 篇文章 0 订阅
1 篇文章 0 订阅

【题目描述】

永无乡包含 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 。

S a m p l e    I n p u t Sample~~Input Sample  Input

5 1
4 3 2 5 1
1 2
7
Q 3 2
Q 2 1
B 2 3
B 1 5
Q 2 1
Q 2 4
Q 2 3

S a m p l e    O u t p u t Sample~~Output Sample  Output

-1
2
5
1
2

【题意分析】

yyborz学的splaydsu
题意就是维护一些联通块,并查询在联通块中的排名。
有合并操作:将两个联通块合并。
这个dsu很暴力:启发式将小树节点一个一个插到大树里进去。因为size[]可以预处理。
还有预处理比较麻烦:我们构建N棵splay,第i棵的根节点位于N+i,这样我们就建立起了对应关系。然后splay的时候如果发现目标节点<=N,那么就意味着旋到了某棵splay的根,然后将这棵splay的根重置。
注意前1至N个节点是虚拟的,真正的节点从N+1开始,从N+1开始只是方便处理根节点。
所以之后插入节点要从2N+1开始。

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
#define MAXN 300200
using namespace std;

int n, m, sz, id[MAXN];

struct _splay {
    
    int size[MAXN], ff[MAXN], father[MAXN], son[MAXN][2], val[MAXN], root[MAXN];
    
    inline void maintain (int x) {
        size[x] = size[son[x][0]] + size[son[x][1]] + 1;
    }
    
    int getfather (int x) {
        return (x == ff[x]) ? x : ff[x] = getfather (ff[x]);
    }
    
    inline void rotate (int x) {
        int y = father[x], z = father[y];
        int k = son[y][1] == x, kk = son[z][1] == y;
        son[z][kk] = x;
        father[x] = z;
        son[y][k] = son[x][k ^ 1];
        father[son[x][k ^ 1]] = y;
        son[x][k ^ 1] = y;
        father[y] = x;
        maintain (y), maintain (x);
    }
    
    inline void splay (int x, int goal) {
        while (father[x] != goal) {
            int y = father[x], z = father[y];
            if (z != goal)
                (son[y][1] == x) ^ (son[z][1] == y)
                    ? rotate (x) : rotate (y);
            rotate (x);
        }
        if (goal <= n) root[goal] = x;
    }
    
    inline void insert (int x, int y) {
        int now = root[y], fa = y;
        while (now && x != val[now]) fa = now, now = son[now][x > val[now]];
        now = ++sz;
        if (fa > n) son[fa][x > val[fa]] = now;
        son[now][0] = son[now][1] = 0,
        size[now] = 1, father[now] = fa,
        val[now] = x, splay (now, y);
    }

    void DFS (int now, int y) {
        if (son[now][0]) DFS (son[now][0], y);
        insert (val[now], y);
        if (son[now][1]) DFS (son[now][1], y);
    }

    inline void merge (int xx, int yy) {
        int x = getfather (xx), y = getfather (yy);
        if (x == y) return;
        if (size[root[x]] > size[root[y]]) swap (x, y);
        ff[x] = y; DFS (root[x], y);
    }
    
    int Kth (int x, int rank) {
        if (rank > size[x]) return -1;
        if (son[x][0] && rank <= size[son[x][0]]) return Kth (son[x][0], rank);
        return (rank <= size[son[x][0]] + 1)
        	? val[x] : Kth (son[x][1], rank - size[son[x][0]] - 1);
    }

}tree;

int main () {
    scanf ("%d%d", &n, &m), sz = n * 2;
    for (register int i = 1; i <= n; i++) {
        int x; scanf ("%d", &x);
        tree.root[i] = n + i, tree.ff[i] = tree.father[n + i] = i;
        tree.val[n + i] = x, tree.size[n + i] = 1, id[x] = i;
    }
    for (register int i = 1; i <= m; i++) {
        int x, y; scanf ("%d%d", &x, &y);
        tree.merge (x, y);
    }
    scanf ("%d", &m);
    for (register int i = 1; i <= m; i++) {
        char opt[5]; int x, y;
        scanf ("%s", opt), scanf ("%d%d", &x, &y);
        if (opt[0] == 'B') tree.merge (x, y);
        if (opt[0] == 'Q') {
            int z = tree.Kth (tree.root[tree.getfather (x)], y);
            if (z == -1) puts ("-1");
            else printf ("%d\n", id[z]);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值