Luogu P3224 [HNOI2012]永无乡

洛谷传送门

题目描述

永无乡包含 n n 座岛,编号从 1 n n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 1 n 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 a a 出发经过若干座(含 0 座)桥可以 到达岛 b b ,则称岛 a 和岛 b b 是连通的。

现在有两种操作:

Bxy 表示在岛 x x 与岛 y 之间修建一座新桥。

Qxk Q x k 表示询问当前与岛 x x 连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x x 连通的岛中重要度排名第 k 小的岛是哪座,请你输出那个岛的编号。

输入输出格式

输入格式:

第一行是用空格隔开的两个正整数 n n m ,分别表示岛的个数以及一开始存在的桥数。

接下来的一行是用空格隔开的 n n 个数,依次描述从岛 1 到岛 n n 的重要度排名。随后的 mm 行每行是用空格隔开的两个正整数 ai bi b i ​,表示一开始就存在一座连接岛 ai a i 和岛 bi b i 的桥。

后面剩下的部分描述操作,该部分的第一行是一个正整数 q q ,表示一共有 q 个操作,接下来的 q q 行依次描述每个操作,操作的 格式如上所述,以大写字母 Q B B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。

输出格式:

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

输入输出样例

输入样例#1:

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

输出样例#1:

-1
2
5
1
2

说明

对于 20% 的数据 n1000,q1000 n ≤ 1000 , q ≤ 1000

对于 100% 的数据 n100000,mn,q300000 n ≤ 100000 , m ≤ n , q ≤ 300000

解题分析

显然我们需要动态维护并查集, 并且查询并查集内第 k k <script type="math/tex" id="MathJax-Element-39">k</script>大。 很显然这样的操作主席树可以胜任。 但我们需要合并两个并查集内的信息, 自然需要合并两棵主席树。

和普通的主席树不一样的是, 我们不需要在前一棵树的基础上建立下一棵主席树, 而只需要建出每个小岛代表的一棵树。 如果我们每次将小的树合并到大的树上去的话, 因为最多合并n次, 所以复杂度是科学的。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define ls tree[now].son[0]
#define rs tree[now].son[1]
#define MX 100005
template <class T>
IN void in(T &x)
{
    x = 0;R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
namespace President_Tree
{
    struct Node
    {
        int son[2], sum;
    }tree[MX << 5];
    int fa[MX], arr, dot, line, q, rk[MX], dat[MX], root[MX];
    int find(const int &now){return fa[now] == now ? now : fa[now] = find(fa[now]);}//并查集操作
    void insert(int &now, const int &lef, const int &rig, const int &pos)//建立一棵新树
    {
        if(!now) now = ++arr;
        tree[now].sum++;
        if(lef == rig) return;
        int mid = lef + rig >> 1;
        if(mid >= pos) insert(ls, lef, mid, pos);
        else insert(rs, mid + 1, rig, pos);
    }
    void merge(int &nw, int pr)
    {// 合并两棵线段树, 其中nw表示目标树, pr表示向nw合并的一棵树。
        if(!nw) return nw = pr, void();
        if(!pr) return;
        tree[nw].sum += tree[pr].sum;
        merge(tree[nw].son[0], tree[pr].son[0]);
        merge(tree[nw].son[1], tree[pr].son[1]);
    }
    int query(const int &now, const int &lef, const int &rig, const int &tar)
    //查询树上第k大
    {
        if(lef == rig) return rk[lef];
        int lf = tree[ls].sum; int mid = lef + rig >> 1;
        if(lf >= tar) return query(ls, lef, mid, tar);
        else return query(rs, mid + 1, rig, tar - lf);
    }
}
using namespace President_Tree;
int main(void)
{
    int a, b, x, y;
    char buf[5];
    in(dot);in(line);
    for (R int i = 1; i <= dot; ++i) fa[i] = i;
    for (R int i = 1; i <= dot; ++i)
    {
        in(dat[i]); rk[dat[i]] = i;
        insert(root[i], 1, dot, dat[i]);
    }
    for (R int i = 1; i <= line; ++i)
    {
        in(a), in(b);
        x = find(a), y = find(b);
        if(tree[root[x]].sum > tree[root[y]].sum)//启发式合并
        merge(root[x], root[y]), fa[y] = x;
        else merge(root[y], root[x]), fa[x] = y;
    }
    in(q);
    W (q--)
    {
        scanf("%s", buf); in(a), in(b);
        switch (buf[0])
        {
        case 'Q':
        {
            if(tree[root[find(a)]].sum < b) printf("-1\n");
            else printf("%d\n", query(root[find(a)], 1, dot, b));
            break;
        }
        case 'B':
        {
            x = find(a), y = find(b);
            if(tree[root[x]].sum > tree[root[y]].sum)
            merge(root[x], root[y]), fa[y] = x;
            else merge(root[y], root[x]), fa[x] = y;
        }
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值