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
    评论
根据引用\[1\]和引用\[2\]的描述,题目中的影魔拥有n个灵魂,每个灵魂有一个战斗力ki。对于任意一对灵魂对i,j (i<j),如果不存在ks (i<s<j)大于ki或者kj,则会为影魔提供p1的攻击力。另一种情况是,如果存在一个位置k,满足ki<c<kj或者kj<c<ki,则会为影魔提供p2的攻击力。其他情况下的灵魂对不会为影魔提供攻击力。 根据引用\[3\]的描述,我们可以从左到右进行枚举。对于情况1,当扫到r\[i\]时,更新l\[i\]的贡献。对于情况2.1,当扫到l\[i\]时,更新区间\[i+1,r\[i\]-1\]的贡献。对于情况2.2,当扫到r\[i\]时,更新区间\[l\[i\]+1,i-1\]的贡献。 因此,对于给定的区间\[l,r\],我们可以根据上述方法计算出区间内所有下标二元组i,j (l<=i<j<=r)的贡献之和。 #### 引用[.reference_title] - *1* *3* [P3722 [AH2017/HNOI2017]影魔(树状数组)](https://blog.csdn.net/li_wen_zhuo/article/details/115446022)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [洛谷3722 AH2017/HNOI2017 影魔 线段树 单调栈](https://blog.csdn.net/forever_shi/article/details/119649910)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值